From a6c55361b634c264b517e045846da0c29c57245d Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Mon, 13 Jan 2025 16:03:37 +0100 Subject: [PATCH 01/55] First run at a setup --- .../search-rankings/request-parameters.php | 115 ++++++++++++ .../search-rankings/search-data-container.php | 48 +++++ .../domain/search-rankings/search-data.php | 86 +++++++++ .../search-rankings/top-page-endpoint.php | 52 ++++++ .../site-kit-search-console-adapter.php | 54 ++++++ .../search-rankings-parser.php | 30 ++++ .../abstract-ranking-route.php | 167 ++++++++++++++++++ .../search-rankings/top-page-route.php | 32 ++++ 8 files changed, 584 insertions(+) create mode 100644 src/dashboard/domain/search-rankings/request-parameters.php create mode 100644 src/dashboard/domain/search-rankings/search-data-container.php create mode 100644 src/dashboard/domain/search-rankings/search-data.php create mode 100644 src/dashboard/infrastructure/endpoints/search-rankings/top-page-endpoint.php create mode 100644 src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php create mode 100644 src/dashboard/infrastructure/search-rankings/search-rankings-parser.php create mode 100644 src/dashboard/user-interface/search-rankings/abstract-ranking-route.php create mode 100644 src/dashboard/user-interface/search-rankings/top-page-route.php diff --git a/src/dashboard/domain/search-rankings/request-parameters.php b/src/dashboard/domain/search-rankings/request-parameters.php new file mode 100644 index 00000000000..f3c5ae07317 --- /dev/null +++ b/src/dashboard/domain/search-rankings/request-parameters.php @@ -0,0 +1,115 @@ +dimensions = $dimensions; + } + + /** + * Getter for the start date. + * + * @return string + */ + public function get_start_date(): string { + return $this->start_date; + } + + /** + * Getter for the end date. + * The date format should be Y-M-D. + * + * @return string + */ + public function get_end_date(): string { + return $this->end_date; + } + + /** + * Getter for the dimensions. + * + * @return string[] + */ + public function get_dimensions(): array { + return $this->dimensions; + } + + /** + * Getter for the result limit. + * + * @return int + */ + public function get_limit(): int { + return $this->limit; + } + + /** + * The start date setter. + * + * @param string $start_date The start date. + * + * @return void + */ + public function set_start_date( string $start_date ): void { + $this->start_date = $start_date; + } + + /** + * The end date setter. + * + * @param string $end_date The end date. + * + * @return void + */ + public function set_end_date( string $end_date ): void { + $this->end_date = $end_date; + } + + /** + * The result limit. + * + * @param int $limit The result limit. + * @return void + */ + public function set_limit( int $limit ): void { + $this->limit = $limit; + } +} diff --git a/src/dashboard/domain/search-rankings/search-data-container.php b/src/dashboard/domain/search-rankings/search-data-container.php new file mode 100644 index 00000000000..176492a0dcb --- /dev/null +++ b/src/dashboard/domain/search-rankings/search-data-container.php @@ -0,0 +1,48 @@ + $search_data_container + */ + private $search_data_container; + + /** + * The constructor + */ + public function __construct() { + $this->search_data_container = []; + } + + /** + * Method to add search data. + * + * @param Search_Data $search_data The data. + * + * @return void + */ + public function add_search_data( Search_Data $search_data ) { + $this->search_data_container[] = $search_data; + } + + /** + * Converts the list to an array. + * + * @return array The array of endpoints. + */ + public function to_array(): array { + $result = []; + foreach ( $this->search_data_container as $search_data ) { + $result[] = $search_data->to_array(); + } + + return $result; + } +} diff --git a/src/dashboard/domain/search-rankings/search-data.php b/src/dashboard/domain/search-rankings/search-data.php new file mode 100644 index 00000000000..82b36410318 --- /dev/null +++ b/src/dashboard/domain/search-rankings/search-data.php @@ -0,0 +1,86 @@ +clicks = $clicks; + $this->ctr = $ctr; + $this->impressions = $impressions; + $this->position = $position; + $this->keys = $keys; + $this->seo_score = 0; + } + + /** + * The array representation of this domain object. + * + * @return array + */ + public function to_array(): array { + return [ + 'clicks' => $this->clicks, + 'ctr' => $this->ctr, + 'impressions' => $this->impressions, + 'position' => $this->position, + 'keys' => $this->keys, + 'seoScore' => $this->seo_score, + ]; + } +} diff --git a/src/dashboard/infrastructure/endpoints/search-rankings/top-page-endpoint.php b/src/dashboard/infrastructure/endpoints/search-rankings/top-page-endpoint.php new file mode 100644 index 00000000000..c22af24ab4b --- /dev/null +++ b/src/dashboard/infrastructure/endpoints/search-rankings/top-page-endpoint.php @@ -0,0 +1,52 @@ +get_namespace() . $this->get_route() ); + } +} diff --git a/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php b/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php new file mode 100644 index 00000000000..d9995c9d089 --- /dev/null +++ b/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php @@ -0,0 +1,54 @@ +context() ); + self::$search_console_module = $modules->get_module( Search_Console::MODULE_SLUG ); + } + + /** + * The wrapper method to add our parameters to a Site Kit API request. + * + * @param Request_Parameters $parameters The parameters. + * + * @return ApiDataRow[]|WP_Error Data on success, or WP_Error on failure. + */ + public function get_data( Request_Parameters $parameters ) { + $data = [ + 'slug' => 'search-console', + 'datapoint' => 'searchanalytics', + 'startDate' => $parameters->get_start_date(), + 'endDate' => $parameters->get_end_date(), + 'limit' => $parameters->get_limit(), + 'dimensions' => $parameters->get_dimensions(), + ]; + + return self::$search_console_module->get_data( 'searchanalytics', $data ); + } +} diff --git a/src/dashboard/infrastructure/search-rankings/search-rankings-parser.php b/src/dashboard/infrastructure/search-rankings/search-rankings-parser.php new file mode 100644 index 00000000000..6cff8622df7 --- /dev/null +++ b/src/dashboard/infrastructure/search-rankings/search-rankings-parser.php @@ -0,0 +1,30 @@ +add_search_data( new Search_Data( $ranking->clicks, $ranking->ctr, $ranking->impressions, $ranking->position, $ranking->keys ) ); + } + + return $search_data_container; + } +} diff --git a/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php new file mode 100644 index 00000000000..f455fb436e3 --- /dev/null +++ b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php @@ -0,0 +1,167 @@ +site_kit_search_console_adapter = $site_kit_search_console_adapter; + $this->search_rankings_parser = $search_rankings_parser; + } + + /** + * Sets the request parameters. + * + * @param Request_Parameters $request_parameters The API request parameters. + * + * @return void + */ + public function set_request_parameters( + Request_Parameters $request_parameters + ) { + $this->request_parameters = $request_parameters; + } + + /** + * Returns the route prefix. + * + * @return string The route prefix. + * + * @throws Exception If the ROUTE_PREFIX constant is not set in the child class. + */ + public static function get_route_prefix() { + $class = static::class; + $prefix = $class::ROUTE_PREFIX; + + if ( $prefix === null ) { + throw new Exception( 'Ranking route without explicit prefix' ); + } + + return $prefix; + } + + /** + * Registers routes for scores. + * + * @return void + */ + public function register_routes() { + \register_rest_route( + self::ROUTE_NAMESPACE, + $this->get_route_prefix(), + [ + [ + 'methods' => 'GET', + 'callback' => [ $this, 'get_rankings' ], + 'permission_callback' => [ $this, 'permission_manage_options' ], + 'args' => [ + 'limit' => [ + 'required' => true, + 'type' => 'int', + 'sanitize_callback' => 'sanitize_text_field', + 'default' => 5, + ], + + ], + ], + ] + ); + } + + /** + * Gets the rankings of a specific amount of pages. + * + * @param WP_REST_Request $request The request object. + * + * @return WP_REST_Response The success or failure response. + */ + public function get_rankings( WP_REST_Request $request ): WP_REST_Response { + try { + $this->request_parameters->set_limit( $request->get_param( 'limit' ) ); + $this->request_parameters->set_start_date( '2024-01-01' ); + $this->request_parameters->set_end_date( '2025-01-01' ); + + $results = $this->site_kit_search_console_adapter->get_data( $this->request_parameters ); + + } catch ( Exception $exception ) { + return new WP_REST_Response( + [ + 'error' => $exception->getMessage(), + ], + $exception->getCode() + ); + } + + $search_data_container = $this->search_rankings_parser->parse( $results ); + + return new WP_REST_Response( + $search_data_container->to_array(), + 200 + ); + } + + /** + * Permission callback. + * + * @return bool True when user has the 'wpseo_manage_options' capability. + */ + public function permission_manage_options() { + return WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ); + } +} diff --git a/src/dashboard/user-interface/search-rankings/top-page-route.php b/src/dashboard/user-interface/search-rankings/top-page-route.php new file mode 100644 index 00000000000..b4a378f7e1d --- /dev/null +++ b/src/dashboard/user-interface/search-rankings/top-page-route.php @@ -0,0 +1,32 @@ +set_request_parameters( new Request_Parameters( [ 'page' ] ) ); + + parent::__construct( $site_kit_search_console_adapter, $search_rankings_parser ); + } +} From 6449947dbfdb40edb5595133e9a8514fe449f2f2 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Tue, 14 Jan 2025 12:42:30 +0100 Subject: [PATCH 02/55] Feedback and use statements --- .../endpoints/search-rankings/top-page-endpoint.php | 4 ++-- .../search-console/site-kit-search-console-adapter.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dashboard/infrastructure/endpoints/search-rankings/top-page-endpoint.php b/src/dashboard/infrastructure/endpoints/search-rankings/top-page-endpoint.php index c22af24ab4b..a51ab849fdd 100644 --- a/src/dashboard/infrastructure/endpoints/search-rankings/top-page-endpoint.php +++ b/src/dashboard/infrastructure/endpoints/search-rankings/top-page-endpoint.php @@ -5,8 +5,8 @@ use Exception; use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_Interface; -use Yoast\WP\SEO\Dashboard\User_Interface\Scores\Abstract_Ranking_Route; -use Yoast\WP\SEO\Dashboard\User_Interface\Scores\Top_Page_Route; +use Yoast\WP\SEO\Dashboard\User_Interface\Search_Rankings\Abstract_Ranking_Route; +use Yoast\WP\SEO\Dashboard\User_Interface\Search_Rankings\Top_Page_Route; /** * Represents the readability scores endpoint. diff --git a/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php b/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php index d9995c9d089..7f34dd4c797 100644 --- a/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php +++ b/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php @@ -40,7 +40,7 @@ public function __construct() { * @return ApiDataRow[]|WP_Error Data on success, or WP_Error on failure. */ public function get_data( Request_Parameters $parameters ) { - $data = [ + $api_parameters = [ 'slug' => 'search-console', 'datapoint' => 'searchanalytics', 'startDate' => $parameters->get_start_date(), @@ -49,6 +49,6 @@ public function get_data( Request_Parameters $parameters ) { 'dimensions' => $parameters->get_dimensions(), ]; - return self::$search_console_module->get_data( 'searchanalytics', $data ); + return self::$search_console_module->get_data( 'searchanalytics', $api_parameters ); } } From d3513b164ada94dea2d08448ffe39831df3733cf Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Tue, 14 Jan 2025 14:02:18 +0100 Subject: [PATCH 03/55] no __construct side effects. --- .../site-kit-search-console-adapter.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php b/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php index 7f34dd4c797..27df24eac53 100644 --- a/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php +++ b/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php @@ -24,12 +24,14 @@ class Site_Kit_Search_Console_Adapter { private static $search_console_module; /** - * The constructor that sets the instance in the adapter. + * The register method that sets the instance in the adapter. */ - public function __construct() { - $site_kit_plugin = Plugin::instance(); - $modules = new Modules( $site_kit_plugin->context() ); - self::$search_console_module = $modules->get_module( Search_Console::MODULE_SLUG ); + public function register() { + if ( self::$search_console_module === null ) { + $site_kit_plugin = Plugin::instance(); + $modules = new Modules( $site_kit_plugin->context() ); + self::$search_console_module = $modules->get_module( Search_Console::MODULE_SLUG ); + } } /** @@ -40,6 +42,9 @@ public function __construct() { * @return ApiDataRow[]|WP_Error Data on success, or WP_Error on failure. */ public function get_data( Request_Parameters $parameters ) { + if ( self::$search_console_module === null ) { + $this->register(); + } $api_parameters = [ 'slug' => 'search-console', 'datapoint' => 'searchanalytics', From af2b9f368a756b3416928952b4bcb24b42d513a6 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Tue, 14 Jan 2025 14:15:46 +0100 Subject: [PATCH 04/55] CS. --- .../search-console/site-kit-search-console-adapter.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php b/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php index 27df24eac53..421247e15fa 100644 --- a/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php +++ b/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php @@ -25,6 +25,8 @@ class Site_Kit_Search_Console_Adapter { /** * The register method that sets the instance in the adapter. + * + * @return void */ public function register() { if ( self::$search_console_module === null ) { From 8afb9bb5d68357b768b94c24e92465fce16394bc Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Tue, 14 Jan 2025 14:34:02 +0100 Subject: [PATCH 05/55] __construct needed but with a class exists. --- .../search-console/site-kit-search-console-adapter.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php b/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php index 421247e15fa..1dbb97180d6 100644 --- a/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php +++ b/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php @@ -28,8 +28,8 @@ class Site_Kit_Search_Console_Adapter { * * @return void */ - public function register() { - if ( self::$search_console_module === null ) { + public function __construct() { + if ( \class_exists( 'Google\Site_Kit\Plugin' ) ) { $site_kit_plugin = Plugin::instance(); $modules = new Modules( $site_kit_plugin->context() ); self::$search_console_module = $modules->get_module( Search_Console::MODULE_SLUG ); @@ -44,9 +44,6 @@ public function register() { * @return ApiDataRow[]|WP_Error Data on success, or WP_Error on failure. */ public function get_data( Request_Parameters $parameters ) { - if ( self::$search_console_module === null ) { - $this->register(); - } $api_parameters = [ 'slug' => 'search-console', 'datapoint' => 'searchanalytics', From 141cda317e775c67e00fc051733a00fea555fece Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Tue, 14 Jan 2025 14:56:34 +0100 Subject: [PATCH 06/55] Add second end point to POC. --- .../search-rankings/top-page-endpoint.php | 2 +- .../search-rankings/top-query-endpoint.php | 52 +++++++++++++++++++ .../search-rankings/top-page-route.php | 2 +- .../search-rankings/top-query-route.php | 32 ++++++++++++ 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 src/dashboard/infrastructure/endpoints/search-rankings/top-query-endpoint.php create mode 100644 src/dashboard/user-interface/search-rankings/top-query-route.php diff --git a/src/dashboard/infrastructure/endpoints/search-rankings/top-page-endpoint.php b/src/dashboard/infrastructure/endpoints/search-rankings/top-page-endpoint.php index a51ab849fdd..ed1c0bfdf68 100644 --- a/src/dashboard/infrastructure/endpoints/search-rankings/top-page-endpoint.php +++ b/src/dashboard/infrastructure/endpoints/search-rankings/top-page-endpoint.php @@ -9,7 +9,7 @@ use Yoast\WP\SEO\Dashboard\User_Interface\Search_Rankings\Top_Page_Route; /** - * Represents the readability scores endpoint. + * Represents the top page results endpoint. */ class Top_Page_Endpoint implements Endpoint_Interface { diff --git a/src/dashboard/infrastructure/endpoints/search-rankings/top-query-endpoint.php b/src/dashboard/infrastructure/endpoints/search-rankings/top-query-endpoint.php new file mode 100644 index 00000000000..96011d6b4a1 --- /dev/null +++ b/src/dashboard/infrastructure/endpoints/search-rankings/top-query-endpoint.php @@ -0,0 +1,52 @@ +get_namespace() . $this->get_route() ); + } +} diff --git a/src/dashboard/user-interface/search-rankings/top-page-route.php b/src/dashboard/user-interface/search-rankings/top-page-route.php index b4a378f7e1d..eb7a0964a02 100644 --- a/src/dashboard/user-interface/search-rankings/top-page-route.php +++ b/src/dashboard/user-interface/search-rankings/top-page-route.php @@ -7,7 +7,7 @@ use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Rankings\Search_Rankings_Parser; /** - * Registers a route to get readability scores. + * Registers a route to get top ranking pages. */ class Top_Page_Route extends Abstract_Ranking_Route { diff --git a/src/dashboard/user-interface/search-rankings/top-query-route.php b/src/dashboard/user-interface/search-rankings/top-query-route.php new file mode 100644 index 00000000000..88ae8a96ed0 --- /dev/null +++ b/src/dashboard/user-interface/search-rankings/top-query-route.php @@ -0,0 +1,32 @@ +set_request_parameters( new Request_Parameters( [ 'query' ] ) ); + + parent::__construct( $site_kit_search_console_adapter, $search_rankings_parser ); + } +} From 8ec1890f6f83a9dce3b12ab08094332a72f95c73 Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Fri, 17 Jan 2025 11:58:14 +0100 Subject: [PATCH 07/55] Allow transpiling all js files inside yoastseo/src --- packages/yoastseo/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoastseo/tsconfig.json b/packages/yoastseo/tsconfig.json index e446460c57e..ed8e9103906 100644 --- a/packages/yoastseo/tsconfig.json +++ b/packages/yoastseo/tsconfig.json @@ -1,6 +1,6 @@ { "include": [ - "src/**.js", + "src/**/*", ], "compilerOptions": { "declaration": true, From b1f419449955fd0d00aa14b4a235d621bbad63d0 Mon Sep 17 00:00:00 2001 From: Christoph Daum Date: Sun, 19 Jan 2025 15:02:11 +0100 Subject: [PATCH 08/55] refactor: remove duplicate/redundant condition Removed second condition with `single_month_title( ' ', false )` in `WPSEO_Replace_Vars::retrieve_date()` as this did not provide any difference. Refs: https://github.com/Yoast/wordpress-seo/issues/21975 --- inc/class-wpseo-replace-vars.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/class-wpseo-replace-vars.php b/inc/class-wpseo-replace-vars.php index 5dd81d78cdf..4b8ee75496b 100644 --- a/inc/class-wpseo-replace-vars.php +++ b/inc/class-wpseo-replace-vars.php @@ -371,7 +371,7 @@ private function retrieve_date() { // Returns a string. $replacement = get_the_date(); } - elseif ( single_month_title( ' ', false ) && single_month_title( ' ', false ) !== '' ) { + elseif ( single_month_title( ' ', false ) ) { // Returns a string. $replacement = single_month_title( ' ', false ); } From 9c9a26ad30846f72791c36d227550f1ccae25aa9 Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Mon, 20 Jan 2025 09:41:48 +0100 Subject: [PATCH 09/55] Specifically only transpile js files --- packages/yoastseo/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoastseo/tsconfig.json b/packages/yoastseo/tsconfig.json index ed8e9103906..3f6e9cd340b 100644 --- a/packages/yoastseo/tsconfig.json +++ b/packages/yoastseo/tsconfig.json @@ -1,6 +1,6 @@ { "include": [ - "src/**/*", + "src/**/*.js", ], "compilerOptions": { "declaration": true, From 7a246202e3bf3ca828cfc5c21f359afa35ccb720 Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Mon, 20 Jan 2025 09:50:29 +0100 Subject: [PATCH 10/55] Also compile vendor folder where the external Turkish stemmer is stored. This stemmer is used inside src --- packages/yoastseo/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/yoastseo/tsconfig.json b/packages/yoastseo/tsconfig.json index 3f6e9cd340b..2bd4773cd2c 100644 --- a/packages/yoastseo/tsconfig.json +++ b/packages/yoastseo/tsconfig.json @@ -1,6 +1,7 @@ { "include": [ "src/**/*.js", + "vendor" ], "compilerOptions": { "declaration": true, From 0fe8fb9fafa4ba9911678882028f3521c411fc47 Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Mon, 20 Jan 2025 09:52:04 +0100 Subject: [PATCH 11/55] Add moduleResolution option to make sure that importing modules with asterisk --- packages/yoastseo/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/yoastseo/tsconfig.json b/packages/yoastseo/tsconfig.json index 2bd4773cd2c..015b1326585 100644 --- a/packages/yoastseo/tsconfig.json +++ b/packages/yoastseo/tsconfig.json @@ -9,7 +9,8 @@ "allowJs": true, "checkJs": false, "outDir": "./types", - "module": "es2022", + "module": "esnext", + "moduleResolution": "Bundler", "strict": true, "skipLibCheck": true, } From 8440d81cb438cd8d28b5df1ae427a30dca9b8e3b Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Mon, 20 Jan 2025 09:59:29 +0100 Subject: [PATCH 12/55] Also remove the generated types folder when running this command --- packages/yoastseo/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoastseo/package.json b/packages/yoastseo/package.json index 46dde0a02c4..470dea68bfb 100644 --- a/packages/yoastseo/package.json +++ b/packages/yoastseo/package.json @@ -28,7 +28,7 @@ "build": "yarn clean && yarn build:js && yarn build:types", "build:js": "babel src --copy-files --source-maps --out-dir build", "build:types": "tsc", - "clean": "rm -rf build", + "clean": "rm -rf build types", "pretest": "grunt get-premium-configuration", "test": "jest", "lint": "eslint . --max-warnings 26" From beaee9a4c3110f41631d9cee6d019cd72bbe214d Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Mon, 20 Jan 2025 11:43:37 +0100 Subject: [PATCH 13/55] Updates the morphology data version number for German --- apps/content-analysis-api/helpers/get-researcher.js | 2 +- packages/yoastseo/spec/specHelpers/getMorphologyData.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/content-analysis-api/helpers/get-researcher.js b/apps/content-analysis-api/helpers/get-researcher.js index 73f4ea1b660..54e97614791 100644 --- a/apps/content-analysis-api/helpers/get-researcher.js +++ b/apps/content-analysis-api/helpers/get-researcher.js @@ -8,7 +8,7 @@ const getLanguagesWithWordComplexity = helpers.getLanguagesWithWordComplexity; const MORPHOLOGY_VERSIONS = { en: "v6", - de: "v10", + de: "v11", es: "v10", fr: "v11", it: "v10", diff --git a/packages/yoastseo/spec/specHelpers/getMorphologyData.js b/packages/yoastseo/spec/specHelpers/getMorphologyData.js index 977b46ab430..bde1d2b7dec 100644 --- a/packages/yoastseo/spec/specHelpers/getMorphologyData.js +++ b/packages/yoastseo/spec/specHelpers/getMorphologyData.js @@ -1,5 +1,5 @@ import en from "../../premium-configuration/data/morphologyData-en-v6.json"; -import de from "../../premium-configuration/data/morphologyData-de-v10.json"; +import de from "../../premium-configuration/data/morphologyData-de-v11.json"; import nl from "../../premium-configuration/data/morphologyData-nl-v9.json"; import es from "../../premium-configuration/data/morphologyData-es-v10.json"; import fr from "../../premium-configuration/data/morphologyData-fr-v11.json"; From a9c394e29f75c97ca7d56a801e6159b217614237 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Mon, 20 Jan 2025 12:05:35 +0100 Subject: [PATCH 14/55] Add a layer of abstraction to split the adapters from the data providers. --- .../domain/data-provider/data-container.php | 48 +++++++++++++++++ .../domain/data-provider/data-interface.php | 16 ++++++ .../data-provider/data-provider-interface.php | 18 +++++++ .../parameters.php} | 29 +--------- .../search-rankings/search-data-container.php | 48 ----------------- .../domain/search-rankings/search-data.php | 4 +- .../search-rankings-data-provider.php | 54 +++++++++++++++++++ .../search-console-parameters.php | 36 +++++++++++++ .../site-kit-search-console-adapter.php | 5 +- .../search-rankings-parser.php | 10 ++-- .../abstract-ranking-route.php | 38 +++++-------- .../search-rankings/top-page-route.php | 14 +++-- .../search-rankings/top-query-route.php | 14 +++-- 13 files changed, 209 insertions(+), 125 deletions(-) create mode 100644 src/dashboard/domain/data-provider/data-container.php create mode 100644 src/dashboard/domain/data-provider/data-interface.php create mode 100644 src/dashboard/domain/data-provider/data-provider-interface.php rename src/dashboard/domain/{search-rankings/request-parameters.php => data-provider/parameters.php} (73%) delete mode 100644 src/dashboard/domain/search-rankings/search-data-container.php create mode 100644 src/dashboard/domain/search-rankings/search-rankings-data-provider.php create mode 100644 src/dashboard/infrastructure/search-console/search-console-parameters.php diff --git a/src/dashboard/domain/data-provider/data-container.php b/src/dashboard/domain/data-provider/data-container.php new file mode 100644 index 00000000000..01b4d9829d8 --- /dev/null +++ b/src/dashboard/domain/data-provider/data-container.php @@ -0,0 +1,48 @@ + $data_container + */ + private $data_container; + + /** + * The constructor + */ + public function __construct() { + $this->data_container = []; + } + + /** + * Method to add data. + * + * @param Data_Interface $data The data. + * + * @return void + */ + public function add_data( Data_Interface $data ) { + $this->data_container[] = $data; + } + + /** + * Converts the list to an array. + * + * @return array The array of endpoints. + */ + public function to_array(): array { + $result = []; + foreach ( $this->data_container as $data ) { + $result[] = $data->to_array(); + } + + return $result; + } +} diff --git a/src/dashboard/domain/data-provider/data-interface.php b/src/dashboard/domain/data-provider/data-interface.php new file mode 100644 index 00000000000..4f9693bc77f --- /dev/null +++ b/src/dashboard/domain/data-provider/data-interface.php @@ -0,0 +1,16 @@ + + */ + public function to_array(): array; +} diff --git a/src/dashboard/domain/data-provider/data-provider-interface.php b/src/dashboard/domain/data-provider/data-provider-interface.php new file mode 100644 index 00000000000..b7d5c444c3a --- /dev/null +++ b/src/dashboard/domain/data-provider/data-provider-interface.php @@ -0,0 +1,18 @@ +dimensions = $dimensions; - } - /** * Getter for the start date. * @@ -63,15 +47,6 @@ public function get_end_date(): string { return $this->end_date; } - /** - * Getter for the dimensions. - * - * @return string[] - */ - public function get_dimensions(): array { - return $this->dimensions; - } - /** * Getter for the result limit. * diff --git a/src/dashboard/domain/search-rankings/search-data-container.php b/src/dashboard/domain/search-rankings/search-data-container.php deleted file mode 100644 index 176492a0dcb..00000000000 --- a/src/dashboard/domain/search-rankings/search-data-container.php +++ /dev/null @@ -1,48 +0,0 @@ - $search_data_container - */ - private $search_data_container; - - /** - * The constructor - */ - public function __construct() { - $this->search_data_container = []; - } - - /** - * Method to add search data. - * - * @param Search_Data $search_data The data. - * - * @return void - */ - public function add_search_data( Search_Data $search_data ) { - $this->search_data_container[] = $search_data; - } - - /** - * Converts the list to an array. - * - * @return array The array of endpoints. - */ - public function to_array(): array { - $result = []; - foreach ( $this->search_data_container as $search_data ) { - $result[] = $search_data->to_array(); - } - - return $result; - } -} diff --git a/src/dashboard/domain/search-rankings/search-data.php b/src/dashboard/domain/search-rankings/search-data.php index 82b36410318..2a72a093c98 100644 --- a/src/dashboard/domain/search-rankings/search-data.php +++ b/src/dashboard/domain/search-rankings/search-data.php @@ -2,10 +2,12 @@ // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Search_Rankings; +use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Interface; + /** * Domain object that represents a single Search Data record. */ -class Search_Data { +class Search_Data implements Data_Interface { /** * The amount of clicks a `key` gets. diff --git a/src/dashboard/domain/search-rankings/search-rankings-data-provider.php b/src/dashboard/domain/search-rankings/search-rankings-data-provider.php new file mode 100644 index 00000000000..75f4eec26a6 --- /dev/null +++ b/src/dashboard/domain/search-rankings/search-rankings-data-provider.php @@ -0,0 +1,54 @@ +site_kit_search_console_adapter = $site_kit_search_console_adapter; + $this->search_rankings_parser = $search_rankings_parser; + } + + /** + * Method to get search related data from a provider. + * + * @param Parameters $parameters The parameter to get the search data for. + * + * @return Data_Container + */ + public function get_data( Parameters $parameters ): Data_Container { + + $results = $this->site_kit_search_console_adapter->get_data( $parameters ); + + return $this->search_rankings_parser->parse( $results ); + } +} diff --git a/src/dashboard/infrastructure/search-console/search-console-parameters.php b/src/dashboard/infrastructure/search-console/search-console-parameters.php new file mode 100644 index 00000000000..2acc51e15ab --- /dev/null +++ b/src/dashboard/infrastructure/search-console/search-console-parameters.php @@ -0,0 +1,36 @@ +dimensions = $dimensions; + } + + /** + * Getter for the dimensions. + * + * @return string[] + */ + public function get_dimensions(): array { + return $this->dimensions; + } +} diff --git a/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php b/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php index 1dbb97180d6..823bb5b980b 100644 --- a/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php +++ b/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php @@ -9,7 +9,6 @@ use Google\Site_Kit\Plugin; use Google\Site_Kit_Dependencies\Google\Service\SearchConsole\ApiDataRow; use WP_Error; -use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Request_Parameters; /** * The site API adapter to make calls via the Site_Kit plugin. @@ -39,11 +38,11 @@ public function __construct() { /** * The wrapper method to add our parameters to a Site Kit API request. * - * @param Request_Parameters $parameters The parameters. + * @param Search_Console_Parameters $parameters The parameters. * * @return ApiDataRow[]|WP_Error Data on success, or WP_Error on failure. */ - public function get_data( Request_Parameters $parameters ) { + public function get_data( Search_Console_Parameters $parameters ) { $api_parameters = [ 'slug' => 'search-console', 'datapoint' => 'searchanalytics', diff --git a/src/dashboard/infrastructure/search-rankings/search-rankings-parser.php b/src/dashboard/infrastructure/search-rankings/search-rankings-parser.php index 6cff8622df7..13f5f9116fa 100644 --- a/src/dashboard/infrastructure/search-rankings/search-rankings-parser.php +++ b/src/dashboard/infrastructure/search-rankings/search-rankings-parser.php @@ -4,8 +4,8 @@ namespace Yoast\WP\SEO\Dashboard\Infrastructure\Search_Rankings; use Google\Site_Kit_Dependencies\Google\Service\SearchConsole\ApiDataRow; +use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container; use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Search_Data; -use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Search_Data_Container; /** * This class contains all logic to parse the raw API response to usable domain objects for the rest of the system. @@ -17,12 +17,12 @@ class Search_Rankings_Parser { * * @param ApiDataRow[] $results The raw results. * - * @return Search_Data_Container The parsed data. + * @return Data_Container The parsed data. */ - public function parse( array $results ): Search_Data_Container { - $search_data_container = new Search_Data_Container(); + public function parse( array $results ): Data_Container { + $search_data_container = new Data_Container(); foreach ( $results as $ranking ) { - $search_data_container->add_search_data( new Search_Data( $ranking->clicks, $ranking->ctr, $ranking->impressions, $ranking->position, $ranking->keys ) ); + $search_data_container->add_data( new Search_Data( $ranking->clicks, $ranking->ctr, $ranking->impressions, $ranking->position, $ranking->keys ) ); } return $search_data_container; diff --git a/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php index f455fb436e3..d92aa6c2932 100644 --- a/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php +++ b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php @@ -7,9 +7,8 @@ use WP_REST_Response; use WPSEO_Capability_Utils; use Yoast\WP\SEO\Conditionals\No_Conditionals; -use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Request_Parameters; -use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Site_Kit_Search_Console_Adapter; -use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Rankings\Search_Rankings_Parser; +use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Search_Rankings_Data_Provider; +use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Search_Console_Parameters; use Yoast\WP\SEO\Main; use Yoast\WP\SEO\Routes\Route_Interface; @@ -36,44 +35,35 @@ abstract class Abstract_Ranking_Route implements Route_Interface { /** * The request parameters. * - * @var Request_Parameters $request_parameters + * @var Search_Console_Parameters $request_parameters */ private $request_parameters; /** - * The API adapter. + * The data provider. * - * @var Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter + * @var Search_Rankings_Data_Provider $search_rankings_data_provider */ - private $site_kit_search_console_adapter; - - /** - * The rankings parser. - * - * @var Search_Rankings_Parser $search_rankings_parser - */ - private $search_rankings_parser; + private $search_rankings_data_provider; /** * The constructor. * - * @param Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter The adapter. - * @param Search_Rankings_Parser $search_rankings_parser The parser. + * @param Search_Rankings_Data_Provider $search_rankings_data_provider The data provider. */ - public function __construct( Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter, Search_Rankings_Parser $search_rankings_parser ) { - $this->site_kit_search_console_adapter = $site_kit_search_console_adapter; - $this->search_rankings_parser = $search_rankings_parser; + public function __construct( Search_Rankings_Data_Provider $search_rankings_data_provider ) { + $this->search_rankings_data_provider = $search_rankings_data_provider; } /** * Sets the request parameters. * - * @param Request_Parameters $request_parameters The API request parameters. + * @param Search_Console_Parameters $request_parameters The API request parameters. * * @return void */ public function set_request_parameters( - Request_Parameters $request_parameters + Search_Console_Parameters $request_parameters ) { $this->request_parameters = $request_parameters; } @@ -109,7 +99,7 @@ public function register_routes() { [ 'methods' => 'GET', 'callback' => [ $this, 'get_rankings' ], - 'permission_callback' => [ $this, 'permission_manage_options' ], + //'permission_callback' => [ $this, 'permission_manage_options' ], 'args' => [ 'limit' => [ 'required' => true, @@ -137,7 +127,7 @@ public function get_rankings( WP_REST_Request $request ): WP_REST_Response { $this->request_parameters->set_start_date( '2024-01-01' ); $this->request_parameters->set_end_date( '2025-01-01' ); - $results = $this->site_kit_search_console_adapter->get_data( $this->request_parameters ); + $search_data_container = $this->search_rankings_data_provider->get_data( $this->request_parameters ); } catch ( Exception $exception ) { return new WP_REST_Response( @@ -148,8 +138,6 @@ public function get_rankings( WP_REST_Request $request ): WP_REST_Response { ); } - $search_data_container = $this->search_rankings_parser->parse( $results ); - return new WP_REST_Response( $search_data_container->to_array(), 200 diff --git a/src/dashboard/user-interface/search-rankings/top-page-route.php b/src/dashboard/user-interface/search-rankings/top-page-route.php index eb7a0964a02..62d71589656 100644 --- a/src/dashboard/user-interface/search-rankings/top-page-route.php +++ b/src/dashboard/user-interface/search-rankings/top-page-route.php @@ -2,9 +2,8 @@ // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Search_Rankings; -use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Request_Parameters; -use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Site_Kit_Search_Console_Adapter; -use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Rankings\Search_Rankings_Parser; +use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Search_Rankings_Data_Provider; +use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Search_Console_Parameters; /** * Registers a route to get top ranking pages. @@ -21,12 +20,11 @@ class Top_Page_Route extends Abstract_Ranking_Route { /** * The constructor. * - * @param Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter The adapter. - * @param Search_Rankings_Parser $search_rankings_parser The parser. + * @param Search_Rankings_Data_Provider $search_rankings_data_provider The data provider. */ - public function __construct( Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter, Search_Rankings_Parser $search_rankings_parser ) { - $this->set_request_parameters( new Request_Parameters( [ 'page' ] ) ); + public function __construct( Search_Rankings_Data_Provider $search_rankings_data_provider ) { + $this->set_request_parameters( new Search_Console_Parameters( [ 'page' ] ) ); - parent::__construct( $site_kit_search_console_adapter, $search_rankings_parser ); + parent::__construct( $search_rankings_data_provider ); } } diff --git a/src/dashboard/user-interface/search-rankings/top-query-route.php b/src/dashboard/user-interface/search-rankings/top-query-route.php index 88ae8a96ed0..91ba75a8d72 100644 --- a/src/dashboard/user-interface/search-rankings/top-query-route.php +++ b/src/dashboard/user-interface/search-rankings/top-query-route.php @@ -2,9 +2,8 @@ // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Search_Rankings; -use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Request_Parameters; -use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Site_Kit_Search_Console_Adapter; -use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Rankings\Search_Rankings_Parser; +use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Search_Rankings_Data_Provider; +use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Search_Console_Parameters; /** * Registers a route to get the top search queries. @@ -21,12 +20,11 @@ class Top_Query_Route extends Abstract_Ranking_Route { /** * The constructor. * - * @param Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter The adapter. - * @param Search_Rankings_Parser $search_rankings_parser The parser. + * @param Search_Rankings_Data_Provider $search_rankings_data_provider The data provider. */ - public function __construct( Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter, Search_Rankings_Parser $search_rankings_parser ) { - $this->set_request_parameters( new Request_Parameters( [ 'query' ] ) ); + public function __construct( Search_Rankings_Data_Provider $search_rankings_data_provider ) { + $this->set_request_parameters( new Search_Console_Parameters( [ 'query' ] ) ); - parent::__construct( $site_kit_search_console_adapter, $search_rankings_parser ); + parent::__construct( $search_rankings_data_provider ); } } From 49966fb858c1d310b803ad86eebe08a1fea5b4a6 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Mon, 20 Jan 2025 13:12:34 +0100 Subject: [PATCH 15/55] Oops. --- .../user-interface/search-rankings/abstract-ranking-route.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php index d92aa6c2932..0f83c271885 100644 --- a/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php +++ b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php @@ -99,7 +99,7 @@ public function register_routes() { [ 'methods' => 'GET', 'callback' => [ $this, 'get_rankings' ], - //'permission_callback' => [ $this, 'permission_manage_options' ], + 'permission_callback' => [ $this, 'permission_manage_options' ], 'args' => [ 'limit' => [ 'required' => true, From e42fc22bbce9de5afaa28898c4fbdd9564bc5a79 Mon Sep 17 00:00:00 2001 From: Enrico Battocchi Date: Mon, 20 Jan 2025 14:43:17 +0100 Subject: [PATCH 16/55] Add changelog heading --- readme.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.txt b/readme.txt index 4a469efb329..78d00bf459b 100644 --- a/readme.txt +++ b/readme.txt @@ -278,6 +278,8 @@ Your question has most likely been answered on our help center: [yoast.com/help/ Release date: 2025-01-21 +Yoast SEO 24.3 brings more enhancements and bugfixes. [Find more information about our software releases and updates here](https://yoa.st/releases). + #### Enhancements * Introduces more robust HTML processing and highlighting for the _sentence length_ and _paragraph length_ assessments. From 29c5e763b093cc590ea2a55cb67ae08789b87389 Mon Sep 17 00:00:00 2001 From: YoastBot Date: Tue, 21 Jan 2025 08:55:13 +0000 Subject: [PATCH 17/55] Bump version to 24.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 64fdb16cb95..89ad9852aaa 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "webpack-bundle-analyzer": "^4.9.1" }, "yoast": { - "pluginVersion": "24.3-RC3" + "pluginVersion": "24.3" }, "version": "0.0.0" } From 9d0dfc41eb5320731b518c0d59ecd9502f77efd7 Mon Sep 17 00:00:00 2001 From: YoastBot Date: Tue, 21 Jan 2025 09:01:12 +0000 Subject: [PATCH 18/55] Bump version to 24.3 on free --- readme.txt | 2 +- wp-seo-main.php | 2 +- wp-seo.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.txt b/readme.txt index 78d00bf459b..e6617591647 100644 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ License: GPLv3 License URI: http://www.gnu.org/licenses/gpl.html Tags: SEO, XML sitemap, Content analysis, Readability, Schema Tested up to: 6.7 -Stable tag: 24.2 +Stable tag: 24.3 Requires PHP: 7.2.5 Improve your WordPress SEO: Write better content and have a fully optimized WordPress site using the Yoast SEO plugin. diff --git a/wp-seo-main.php b/wp-seo-main.php index 458db88253d..239a0cecf44 100644 --- a/wp-seo-main.php +++ b/wp-seo-main.php @@ -15,7 +15,7 @@ * {@internal Nobody should be able to overrule the real version number as this can cause * serious issues with the options, so no if ( ! defined() ).}} */ -define( 'WPSEO_VERSION', '24.3-RC3' ); +define( 'WPSEO_VERSION', '24.3' ); if ( ! defined( 'WPSEO_PATH' ) ) { diff --git a/wp-seo.php b/wp-seo.php index cb4b4bb678c..feb220a7913 100644 --- a/wp-seo.php +++ b/wp-seo.php @@ -8,7 +8,7 @@ * * @wordpress-plugin * Plugin Name: Yoast SEO - * Version: 24.3-RC3 + * Version: 24.3 * Plugin URI: https://yoa.st/1uj * Description: The first true all-in-one SEO solution for WordPress, including on-page content analysis, XML sitemaps and much more. * Author: Team Yoast From 6b217db12d86135dfe0c99e2d29f78d26211cb30 Mon Sep 17 00:00:00 2001 From: YoastBot Date: Tue, 21 Jan 2025 10:22:17 +0000 Subject: [PATCH 19/55] Add changelog --- readme.txt | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/readme.txt b/readme.txt index e6617591647..7f5ee8b44d7 100644 --- a/readme.txt +++ b/readme.txt @@ -274,6 +274,18 @@ Your question has most likely been answered on our help center: [yoast.com/help/ == Changelog == += 24.4 = + +Release date: 2025-02-04 + +#### Bugfixes + +* Fixes a bug where editing a link in the block editor would have unexpected consequences when using undo. +* Fixes a bug where the `article:modified_time` meta tag would show an earlier time than the `article:published_time` meta tag for scheduled posts. +* Fixes a bug where the Schema type of the product would not be locked when Yoast WooCommerce SEO is enabled. +* Fixes a bug where users without Yoast WooCommerce SEO are not greeted with the AI upsell when trying to use the AI feature and are actually able to access the feature, if on a non-English site. +* Fixes a bug where Yoast WooCommerce SEO upsell would not render when on a locale that translates the product post label. + = 24.3 = Release date: 2025-01-21 @@ -295,26 +307,5 @@ Yoast SEO 24.3 brings more enhancements and bugfixes. [Find more information abo * Improves contrast in the related keyphrase suggestions table. * Improves contrast in the RSS supported variables explainer table. -= 24.2 = - -Release date: 2025-01-07 - -Yoast SEO 24.2 brings more enhancements and bugfixes. [Find more information about our software releases and updates here](https://yoa.st/release-7-1-25). - -#### Enhancements - -* Improves the tooltips accessibility in the related keyphrase suggestions modal. - -#### Bugfixes - -* Fixes a bug where a deprecation message would appear in PHP 8+ when saving a post containing images with invalid sources. Props to [kkmuffme](https://github.com/kkmuffme). -* Fixes a bug where a TypeError would occur when checking for capabilities of SEO Manager user role when the roles were not passed as an array. Props to [kfeinUI](https://github.com/kfeinUI). -* Fixes a bug where styles on buttons, intent badge and modal links would not adjust the direction when on RTL view. - -#### Other - -* Changes the title text on the Yoast installation success page. -* Fixes a console warning about ReactDOM.render being no longer supported in React 18. - = Earlier versions = For the changelog of earlier versions, please refer to [the changelog on yoast.com](https://yoa.st/yoast-seo-changelog). From 0031b1b85c60d127dcd458cbd89a54d3099c9afe Mon Sep 17 00:00:00 2001 From: YoastBot Date: Tue, 21 Jan 2025 10:22:22 +0000 Subject: [PATCH 20/55] Bump version to 24.4-RC1 --- package.json | 2 +- wp-seo-main.php | 2 +- wp-seo.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 1b4466a0a47..dabdde8b0b2 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "webpack-bundle-analyzer": "^4.9.1" }, "yoast": { - "pluginVersion": "24.3" + "pluginVersion": "24.4-RC1" }, "version": "0.0.0" } diff --git a/wp-seo-main.php b/wp-seo-main.php index 239a0cecf44..3cc74c8e926 100644 --- a/wp-seo-main.php +++ b/wp-seo-main.php @@ -15,7 +15,7 @@ * {@internal Nobody should be able to overrule the real version number as this can cause * serious issues with the options, so no if ( ! defined() ).}} */ -define( 'WPSEO_VERSION', '24.3' ); +define( 'WPSEO_VERSION', '24.4-RC1' ); if ( ! defined( 'WPSEO_PATH' ) ) { diff --git a/wp-seo.php b/wp-seo.php index feb220a7913..aeae5b11beb 100644 --- a/wp-seo.php +++ b/wp-seo.php @@ -8,7 +8,7 @@ * * @wordpress-plugin * Plugin Name: Yoast SEO - * Version: 24.3 + * Version: 24.4-RC1 * Plugin URI: https://yoa.st/1uj * Description: The first true all-in-one SEO solution for WordPress, including on-page content analysis, XML sitemaps and much more. * Author: Team Yoast @@ -20,7 +20,7 @@ * Requires PHP: 7.2.5 * * WC requires at least: 7.1 - * WC tested up to: 9.5 + * WC tested up to: 9.6 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From 4106aac066ec811eb75b03543f8fbd510b879906 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 3 Jan 2025 09:44:21 +0200 Subject: [PATCH 21/55] WIP site kit tables --- .../js/src/dashboard/components/dashboard.js | 52 ++++++++++++++ .../scores/components/site-kit-table.js | 68 +++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 packages/js/src/dashboard/scores/components/site-kit-table.js diff --git a/packages/js/src/dashboard/components/dashboard.js b/packages/js/src/dashboard/components/dashboard.js index 62f58e02edc..bc9b4dd4f21 100644 --- a/packages/js/src/dashboard/components/dashboard.js +++ b/packages/js/src/dashboard/components/dashboard.js @@ -1,4 +1,6 @@ +import { __ } from "@wordpress/i18n"; import { Scores } from "../scores/components/scores"; +import { SiteKitTable } from "../scores/components/site-kit-table"; import { PageTitle } from "./page-title"; /** @@ -18,6 +20,53 @@ import { PageTitle } from "./page-title"; * @returns {JSX.Element} The element. */ export const Dashboard = ( { contentTypes, userName, features, endpoints, headers, links } ) => { + + const columnsNames = [ + __( "Landing page", "wordpress-seo" ), + __( "Clicks", "wordpress-seo" ), + __( "Impressions", "wordpress-seo" ), + __( "CTR", "wordpress-seo" ), + __( "Average position", "wordpress-seo" ), + __( "SEO score", "wordpress-seo" ), + ]; + + const data = [ + [ "https://example.com", + 100, + 1000, + 10, + 1, + 100, + ], + [ "https://example.com", + 100, + 1000, + 10, + 1, + 100, + ], + [ "https://example.com", + 100, + 1000, + 10, + 1, + 100, + ], + [ "https://example.com", + 100, + 1000, + 10, + 1, + 100, + ], + [ "https://example.com", + 100, + 1000, + 10, + 1, + 100, + ], + ]; return ( <> @@ -28,7 +77,10 @@ export const Dashboard = ( { contentTypes, userName, features, endpoints, header { features.indexables && features.readabilityAnalysis && ( ) } + + + ); }; diff --git a/packages/js/src/dashboard/scores/components/site-kit-table.js b/packages/js/src/dashboard/scores/components/site-kit-table.js new file mode 100644 index 00000000000..5adbde200ab --- /dev/null +++ b/packages/js/src/dashboard/scores/components/site-kit-table.js @@ -0,0 +1,68 @@ +import { ChevronDownIcon } from '@heroicons/react/solid'; +import { __ } from '@wordpress/i18n'; +import { Paper, Table, Title } from '@yoast/ui-library'; + + +const FirstCell = ( { value, index } ) => ( + + { index + 1 } + { value } + +); + +const LastCell = ( { score } ) => ( +
+ { score } +
+ +); + + +/** + * The Site Kit table component. + * + * @param {string} title The component props. + * @param {[string]} columnsNames The component props. + * @param {[object]} data The component props. + * + * @returns {JSX.Element} The element. + */ +export const SiteKitTable = ( { title, columnsNames, data } ) => { + return ( + + + { title } + + + + + + { columnsNames.map( ( columnName ) => + +
+ { columnName } + { columnName === __( "Clicks", "wordpress-seo") && } +
+
+ ) } +
+
+ + + { data.map( ( row, rowIndex ) => ( + + { row.map( ( cell, cellIndex ) => ( + + { cellIndex === 0 && } + + ))} + + + ) ) } + +
+
+ ); +}; From 9736b1b1d9a6ff5c02f8b6001906094e38b7618e Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 6 Jan 2025 16:19:29 +0200 Subject: [PATCH 22/55] WIP site kit table component --- css/src/general-page.css | 20 +++++ .../js/src/dashboard/components/dashboard.js | 28 +++---- .../scores/components/site-kit-table.js | 78 +++++++++---------- 3 files changed, 72 insertions(+), 54 deletions(-) diff --git a/css/src/general-page.css b/css/src/general-page.css index d134b01a032..66162ad56cf 100644 --- a/css/src/general-page.css +++ b/css/src/general-page.css @@ -248,6 +248,26 @@ body.toplevel_page_wpseo_dashboard { } } + + .yst-site-kit-widget-table .yst-numbered-cell:first-child { + counter-increment: row-number; + @apply yst-relative yst-text-slate-900 yst-font-medium; + } + + .yst-site-kit-widget-table .yst-numbered-cell:first-child, .yst-site-kit-widget-table th:first-child{ + padding-left: 22px!important; + } + + .yst-numbered-cell:first-child::before { + content: counter(row-number) ". "; + @apply yst-absolute yst-left-0 yst-text-slate-500 yst-font-normal; + } + + /* Initialize the counter at the parent element level */ + .yst-parent-numbered-cell { + counter-reset: row-number; + } + /* RTL */ &.rtl { diff --git a/packages/js/src/dashboard/components/dashboard.js b/packages/js/src/dashboard/components/dashboard.js index bc9b4dd4f21..38fd979b363 100644 --- a/packages/js/src/dashboard/components/dashboard.js +++ b/packages/js/src/dashboard/components/dashboard.js @@ -20,47 +20,46 @@ import { PageTitle } from "./page-title"; * @returns {JSX.Element} The element. */ export const Dashboard = ( { contentTypes, userName, features, endpoints, headers, links } ) => { - - const columnsNames = [ - __( "Landing page", "wordpress-seo" ), - __( "Clicks", "wordpress-seo" ), - __( "Impressions", "wordpress-seo" ), - __( "CTR", "wordpress-seo" ), - __( "Average position", "wordpress-seo" ), - __( "SEO score", "wordpress-seo" ), + const columns = [ + { name: "landing-page", label: __( "Landing page", "wordpress-seo" ) }, + { name: "clicks", label: __( "Clicks", "wordpress-seo" ), sortable: true }, + { name: "impressions", label: __( "Impressions", "wordpress-seo" ) }, + { name: "ctr", label: __( "CTR", "wordpress-seo" ) }, + { name: "average-position", label: __( "Average position", "wordpress-seo" ) }, + { name: "seo-score", label: __( "SEO score", "wordpress-seo" ) }, ]; const data = [ [ "https://example.com", - 100, + 10, 1000, 10, 1, 100, ], [ "https://example.com", - 100, + 90, 1000, 10, 1, 100, ], [ "https://example.com", - 100, + 50, 1000, 10, 1, 100, ], [ "https://example.com", - 100, + 0, 1000, 10, 1, 100, ], [ "https://example.com", - 100, + 70, 1000, 10, 1, @@ -71,6 +70,7 @@ export const Dashboard = ( { contentTypes, userName, features, endpoints, header <>
+ { features.indexables && features.seoAnalysis && ( ) } @@ -78,7 +78,7 @@ export const Dashboard = ( { contentTypes, userName, features, endpoints, header ) } - +
diff --git a/packages/js/src/dashboard/scores/components/site-kit-table.js b/packages/js/src/dashboard/scores/components/site-kit-table.js index 5adbde200ab..ce2b9d295ad 100644 --- a/packages/js/src/dashboard/scores/components/site-kit-table.js +++ b/packages/js/src/dashboard/scores/components/site-kit-table.js @@ -1,64 +1,58 @@ -import { ChevronDownIcon } from '@heroicons/react/solid'; -import { __ } from '@wordpress/i18n'; -import { Paper, Table, Title } from '@yoast/ui-library'; +import { ChevronDownIcon } from "@heroicons/react/solid"; +import { Paper, Table, Title } from "@yoast/ui-library"; - -const FirstCell = ( { value, index } ) => ( - - { index + 1 } - { value } - -); - -const LastCell = ( { score } ) => ( -
- { score } +/** + * The sortable header component. + * @param {string} columnName The column name. + * @returns {JSX.Element} The element. + */ +const SortableHeader = ( { columnName } ) => ( +
+ { columnName } +
- + ); /** * The Site Kit table component. - * - * @param {string} title The component props. - * @param {[string]} columnsNames The component props. - * @param {[object]} data The component props. - * + * + * @param {string} title The table title. + * @param {[object]} columns The columns properties. + * @param {[object]} data The rows data. + * * @returns {JSX.Element} The element. */ -export const SiteKitTable = ( { title, columnsNames, data } ) => { +const SiteKitTable = ( { title, columns, data } ) => { return ( - - + <Paper className="yst-grow yst-max-w-screen-sm yst-p-8 yst-shadow-md"> + <Title as="h3" size="2"> { title } - - +
+ - { columnsNames.map( ( columnName ) => - -
- { columnName } - { columnName === __( "Clicks", "wordpress-seo") && } -
-
+ { columns.map( ( column ) => + + { column.sortable ? : column.label } + + ) }
- + { data.map( ( row, rowIndex ) => ( { row.map( ( cell, cellIndex ) => ( - - { cellIndex === 0 && } - - ))} - + + { cell } + ) ) } ) ) } @@ -66,3 +60,7 @@ export const SiteKitTable = ( { title, columnsNames, data } ) => { ); }; + +SiteKitTable.SortableHeader = SortableHeader; + +export { SiteKitTable }; From 6cf6219c5a15cbea0ebf011a383337b0847a60c9 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Tue, 7 Jan 2025 14:37:12 +0200 Subject: [PATCH 23/55] add sorting functionality and fix layout --- css/src/general-page.css | 11 ++- .../js/src/dashboard/components/dashboard.js | 68 ++------------ .../src/dashboard/scores/components/scores.js | 64 ++++++------- .../scores/components/site-kit-table.js | 91 +++++++++++++++---- 4 files changed, 125 insertions(+), 109 deletions(-) diff --git a/css/src/general-page.css b/css/src/general-page.css index 66162ad56cf..61e1e649e3d 100644 --- a/css/src/general-page.css +++ b/css/src/general-page.css @@ -251,14 +251,17 @@ body.toplevel_page_wpseo_dashboard { .yst-site-kit-widget-table .yst-numbered-cell:first-child { counter-increment: row-number; - @apply yst-relative yst-text-slate-900 yst-font-medium; + @apply yst-relative; } + .yst-site-kit-widget-table .yst-numbered-cell:first-child div { + @apply yst-text-slate-900 yst-font-medium; + } .yst-site-kit-widget-table .yst-numbered-cell:first-child, .yst-site-kit-widget-table th:first-child{ padding-left: 22px!important; } - .yst-numbered-cell:first-child::before { + .yst-site-kit-widget-table .yst-numbered-cell:first-child::before { content: counter(row-number) ". "; @apply yst-absolute yst-left-0 yst-text-slate-500 yst-font-normal; } @@ -268,6 +271,10 @@ body.toplevel_page_wpseo_dashboard { counter-reset: row-number; } + .yst-site-kit-widget-table th:last-child div { + @apply yst-text-end; + } + /* RTL */ &.rtl { diff --git a/packages/js/src/dashboard/components/dashboard.js b/packages/js/src/dashboard/components/dashboard.js index 38fd979b363..a62768a1833 100644 --- a/packages/js/src/dashboard/components/dashboard.js +++ b/packages/js/src/dashboard/components/dashboard.js @@ -1,6 +1,4 @@ -import { __ } from "@wordpress/i18n"; import { Scores } from "../scores/components/scores"; -import { SiteKitTable } from "../scores/components/site-kit-table"; import { PageTitle } from "./page-title"; /** @@ -20,67 +18,19 @@ import { PageTitle } from "./page-title"; * @returns {JSX.Element} The element. */ export const Dashboard = ( { contentTypes, userName, features, endpoints, headers, links } ) => { - const columns = [ - { name: "landing-page", label: __( "Landing page", "wordpress-seo" ) }, - { name: "clicks", label: __( "Clicks", "wordpress-seo" ), sortable: true }, - { name: "impressions", label: __( "Impressions", "wordpress-seo" ) }, - { name: "ctr", label: __( "CTR", "wordpress-seo" ) }, - { name: "average-position", label: __( "Average position", "wordpress-seo" ) }, - { name: "seo-score", label: __( "SEO score", "wordpress-seo" ) }, - ]; - - const data = [ - [ "https://example.com", - 10, - 1000, - 10, - 1, - 100, - ], - [ "https://example.com", - 90, - 1000, - 10, - 1, - 100, - ], - [ "https://example.com", - 50, - 1000, - 10, - 1, - 100, - ], - [ "https://example.com", - 0, - 1000, - 10, - 1, - 100, - ], - [ "https://example.com", - 70, - 1000, - 10, - 1, - 100, - ], - ]; return ( <> -
- - { features.indexables && features.seoAnalysis && ( - - ) } - { features.indexables && features.readabilityAnalysis && ( - - ) } - - +
+
+ { features.indexables && features.seoAnalysis && ( + + ) } + { features.indexables && features.readabilityAnalysis && ( + + ) } +
- ); }; diff --git a/packages/js/src/dashboard/scores/components/scores.js b/packages/js/src/dashboard/scores/components/scores.js index a2d0917a3ad..fc986e02d20 100644 --- a/packages/js/src/dashboard/scores/components/scores.js +++ b/packages/js/src/dashboard/scores/components/scores.js @@ -107,39 +107,41 @@ export const Scores = ( { analysisType, contentTypes, endpoint, headers } ) => { }, [ selectedContentType.name ] ); return ( - - - { analysisType === "readability" - ? __( "Readability scores", "wordpress-seo" ) - : __( "SEO scores", "wordpress-seo" ) - } - -
- - { selectedContentType.taxonomy && selectedContentType.taxonomy?.links?.search && - +
+ + { analysisType === "readability" + ? __( "Readability scores", "wordpress-seo" ) + : __( "SEO scores", "wordpress-seo" ) + } + +
+ - } -
-
- - { ! error && ( - - ) } + { selectedContentType.taxonomy && selectedContentType.taxonomy?.links?.search && + + } +
+
+ + { ! error && ( + + ) } +
); diff --git a/packages/js/src/dashboard/scores/components/site-kit-table.js b/packages/js/src/dashboard/scores/components/site-kit-table.js index ce2b9d295ad..c54834a8576 100644 --- a/packages/js/src/dashboard/scores/components/site-kit-table.js +++ b/packages/js/src/dashboard/scores/components/site-kit-table.js @@ -1,20 +1,45 @@ -import { ChevronDownIcon } from "@heroicons/react/solid"; +import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/solid"; +import { useState, useCallback } from "@wordpress/element"; +import classNames from "classnames"; +import { SCORE_META } from "../score-meta"; import { Paper, Table, Title } from "@yoast/ui-library"; /** * The sortable header component. + * * @param {string} columnName The column name. + * @param {boolean} isAscending Whether the sorting is ascending. + * @param {Function} onClick The click handler. + * @param {number} index The index of the column. * @returns {JSX.Element} The element. */ -const SortableHeader = ( { columnName } ) => ( -
+const SortableHeader = ( { columnName, isAscending, onClick, index } ) => { + const handleSort = useCallback( () => { + onClick( index ); + }, [ onClick, index ] ); + + const ChevronIcon = isAscending ? ChevronDownIcon : ChevronUpIcon; + + return
+ +
; +}; +/** + * The score bullet component. + * + * @param {string} score The score. + * @returns {JSX.Element} The element. + */ +const ScoreBullet = ( { score } ) => ( +
+ + { SCORE_META[ score ].label } + +
); - /** * The Site Kit table component. * @@ -25,33 +50,65 @@ const SortableHeader = ( { columnName } ) => ( * @returns {JSX.Element} The element. */ const SiteKitTable = ( { title, columns, data } ) => { + const [ sortConfig, setSortConfig ] = useState( { key: null, direction: "ascending" } ); + + const sortedData = [ ...data ].sort( ( a, b ) => { + if ( sortConfig.key === null ) { + return 0; + } + const aValue = a[ sortConfig.key ]; + const bValue = b[ sortConfig.key ]; + if ( aValue < bValue ) { + return sortConfig.direction === "ascending" ? -1 : 1; + } + if ( aValue > bValue ) { + return sortConfig.direction === "ascending" ? 1 : -1; + } + return 0; + } ); + + const handleSort = useCallback( ( columnIndex ) => { + let direction = "ascending"; + if ( sortConfig.key === columnIndex && sortConfig.direction === "ascending" ) { + direction = "descending"; + } + setSortConfig( { key: columnIndex, direction } ); + }, [ sortConfig, setSortConfig ] ); + return ( - - + <Paper className="yst-grow yst-p-8 yst-shadow-md"> + <Title as="h3" size="2" className="yst-text-slate-900 yst-font-medium"> { title }
- - { columns.map( ( column ) => + { columns.map( ( column, index ) => - { column.sortable ? : column.label } - + { column.sortable ? ( + + ) :
{ column.label }
}
- ) } + ) }
- - { data.map( ( row, rowIndex ) => ( + { sortedData.map( ( row, rowIndex ) => ( { row.map( ( cell, cellIndex ) => ( - { cell } + { columns[ cellIndex ].name === "seo-score" ? :
{ cell }
}
) ) }
) ) } From e73b7d11436e2f527e6bcc4df1cd05b46b43d583 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Tue, 7 Jan 2025 14:49:26 +0200 Subject: [PATCH 24/55] simplify sort direction prop --- .../scores/components/site-kit-table.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/js/src/dashboard/scores/components/site-kit-table.js b/packages/js/src/dashboard/scores/components/site-kit-table.js index c54834a8576..3d4fdfec88f 100644 --- a/packages/js/src/dashboard/scores/components/site-kit-table.js +++ b/packages/js/src/dashboard/scores/components/site-kit-table.js @@ -50,7 +50,7 @@ const ScoreBullet = ( { score } ) => ( * @returns {JSX.Element} The element. */ const SiteKitTable = ( { title, columns, data } ) => { - const [ sortConfig, setSortConfig ] = useState( { key: null, direction: "ascending" } ); + const [ sortConfig, setSortConfig ] = useState( { key: null, ascending: true } ); const sortedData = [ ...data ].sort( ( a, b ) => { if ( sortConfig.key === null ) { @@ -59,20 +59,20 @@ const SiteKitTable = ( { title, columns, data } ) => { const aValue = a[ sortConfig.key ]; const bValue = b[ sortConfig.key ]; if ( aValue < bValue ) { - return sortConfig.direction === "ascending" ? -1 : 1; + return sortConfig.ascending ? -1 : 1; } if ( aValue > bValue ) { - return sortConfig.direction === "ascending" ? 1 : -1; + return sortConfig.ascending ? 1 : -1; } return 0; } ); const handleSort = useCallback( ( columnIndex ) => { - let direction = "ascending"; - if ( sortConfig.key === columnIndex && sortConfig.direction === "ascending" ) { - direction = "descending"; + let ascending = true; + if ( sortConfig.key === columnIndex && sortConfig.ascending ) { + ascending = false; } - setSortConfig( { key: columnIndex, direction } ); + setSortConfig( { key: columnIndex, ascending } ); }, [ sortConfig, setSortConfig ] ); return ( @@ -91,7 +91,7 @@ const SiteKitTable = ( { title, columns, data } ) => { { column.sortable ? ( From 462b89a321e8363179dc861fa22252b7bf11c6d9 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Tue, 7 Jan 2025 15:15:15 +0200 Subject: [PATCH 25/55] add tests to SiteKitTable --- .../scores/components/site-kit-table.js | 6 +-- .../scores/components/site-kit-table.test.js | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 packages/js/tests/dashboard/scores/components/site-kit-table.test.js diff --git a/packages/js/src/dashboard/scores/components/site-kit-table.js b/packages/js/src/dashboard/scores/components/site-kit-table.js index 3d4fdfec88f..b0f62cbfd7b 100644 --- a/packages/js/src/dashboard/scores/components/site-kit-table.js +++ b/packages/js/src/dashboard/scores/components/site-kit-table.js @@ -49,7 +49,7 @@ const ScoreBullet = ( { score } ) => ( * * @returns {JSX.Element} The element. */ -const SiteKitTable = ( { title, columns, data } ) => { +export const SiteKitTable = ( { title, columns, data } ) => { const [ sortConfig, setSortConfig ] = useState( { key: null, ascending: true } ); const sortedData = [ ...data ].sort( ( a, b ) => { @@ -117,7 +117,3 @@ const SiteKitTable = ( { title, columns, data } ) => { ); }; - -SiteKitTable.SortableHeader = SortableHeader; - -export { SiteKitTable }; diff --git a/packages/js/tests/dashboard/scores/components/site-kit-table.test.js b/packages/js/tests/dashboard/scores/components/site-kit-table.test.js new file mode 100644 index 00000000000..d429223c8b1 --- /dev/null +++ b/packages/js/tests/dashboard/scores/components/site-kit-table.test.js @@ -0,0 +1,53 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import { SiteKitTable } from "../../../../src/dashboard/scores/components/site-kit-table.js"; +import { SCORE_META } from "../../../../src/dashboard/scores/score-meta.js"; + +describe( "SiteKitTable", () => { + const columns = [ + { name: "landing-page", label: "Landing page" }, + { name: "clicks", label: "Clicks", sortable: true }, + { name: "impressions", label: "Impressions" }, + { name: "ctr", label: "CTR" }, + { name: "average-position", label: "Average position" }, + { name: "seo-score", label: "SEO score" }, + ]; + + const data = [ + [ "https://example.com1", 10, 1001, 10, 1, "good" ], + [ "https://example.com2", 90, 1002, 9, 3, "bad" ], + [ "https://example.com3", 50, 1003, 8, 5, "good" ], + [ "https://example.com4", 0, 1004, 7, 2, "ok" ], + [ "https://example.com5", 70, 1005, 6, 4, "ok" ], + ]; + + it( "renders the table with correct title and columns", () => { + render( ); + expect( screen.getByText( "Test Table" ) ).toBeInTheDocument(); + columns.forEach( ( column ) => { + expect( screen.getByText( column.label ) ).toBeInTheDocument(); + } ); + } ); + + it( "renders the table rows correctly", () => { + render( ); + data.forEach( ( row ) => { + const rowElement = screen.getByText( row[ 0 ] ).closest( "tr" ); + expect( rowElement ).toHaveTextContent( row[ 0 ] ); + expect( rowElement ).toHaveTextContent( String( row[ 1 ] ) ); + expect( rowElement ).toHaveTextContent( String( row[ 2 ] ) ); + expect( rowElement ).toHaveTextContent( String( row[ 3 ] ) ); + expect( rowElement ).toHaveTextContent( String( row[ 4 ] ) ); + expect( rowElement ).toHaveTextContent( SCORE_META[ row[ 5 ] ].label ); + } ); + } ); + + it( "sorts the table correctly when a sortable column header is clicked", () => { + render( ); + const clicksHeader = screen.getByText( "Clicks" ); + fireEvent.click( clicksHeader ); + const sortedData = data.sort( ( a, b ) => a[ 1 ] - b[ 1 ] ); + const firstRow = screen.getAllByRole( "row" )[ 1 ]; + expect( firstRow ).toHaveTextContent( sortedData[ 0 ][ 0 ] ); + } ); +} ); From 848e423451db3bf29593fdbc83cae42688835c41 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Tue, 7 Jan 2025 16:04:45 +0200 Subject: [PATCH 26/55] remove hard coded padding --- css/src/general-page.css | 6 +++--- .../dashboard/scores/components/site-kit-table.js | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/css/src/general-page.css b/css/src/general-page.css index 61e1e649e3d..0534bf00ef3 100644 --- a/css/src/general-page.css +++ b/css/src/general-page.css @@ -254,11 +254,11 @@ body.toplevel_page_wpseo_dashboard { @apply yst-relative; } - .yst-site-kit-widget-table .yst-numbered-cell:first-child div { + .yst-site-kit-widget-table .yst-numbered-cell:first-child span { @apply yst-text-slate-900 yst-font-medium; } - .yst-site-kit-widget-table .yst-numbered-cell:first-child, .yst-site-kit-widget-table th:first-child{ - padding-left: 22px!important; + .yst-site-kit-widget-table .yst-numbered-cell:first-child span, .yst-site-kit-widget-table th:first-child span{ + @apply yst-ps-3; } .yst-site-kit-widget-table .yst-numbered-cell:first-child::before { diff --git a/packages/js/src/dashboard/scores/components/site-kit-table.js b/packages/js/src/dashboard/scores/components/site-kit-table.js index b0f62cbfd7b..284ece9d61f 100644 --- a/packages/js/src/dashboard/scores/components/site-kit-table.js +++ b/packages/js/src/dashboard/scores/components/site-kit-table.js @@ -20,10 +20,10 @@ const SortableHeader = ( { columnName, isAscending, onClick, index } ) => { const ChevronIcon = isAscending ? ChevronDownIcon : ChevronUpIcon; - return
; + ; }; /** @@ -33,11 +33,11 @@ const SortableHeader = ( { columnName, isAscending, onClick, index } ) => { * @returns {JSX.Element} The element. */ const ScoreBullet = ( { score } ) => ( -
+ { SCORE_META[ score ].label } -
+ ); /** @@ -95,7 +95,7 @@ export const SiteKitTable = ( { title, columns, data } ) => { index={ index } onClick={ handleSort } /> - ) :
{ column.label }
} + ) : { column.label } } ) } @@ -105,10 +105,10 @@ export const SiteKitTable = ( { title, columns, data } ) => { { row.map( ( cell, cellIndex ) => ( - { columns[ cellIndex ].name === "seo-score" ? :
: { cell }
} + >{ cell } }
) ) }
) ) } From d9c56400169c2b960646de50d3ad7a2e66173214 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Tue, 7 Jan 2025 16:16:44 +0200 Subject: [PATCH 27/55] center bullets --- css/src/general-page.css | 4 ---- packages/js/src/dashboard/scores/components/site-kit-table.js | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/css/src/general-page.css b/css/src/general-page.css index 0534bf00ef3..2319634e193 100644 --- a/css/src/general-page.css +++ b/css/src/general-page.css @@ -271,10 +271,6 @@ body.toplevel_page_wpseo_dashboard { counter-reset: row-number; } - .yst-site-kit-widget-table th:last-child div { - @apply yst-text-end; - } - /* RTL */ &.rtl { diff --git a/packages/js/src/dashboard/scores/components/site-kit-table.js b/packages/js/src/dashboard/scores/components/site-kit-table.js index 284ece9d61f..92b152c7c80 100644 --- a/packages/js/src/dashboard/scores/components/site-kit-table.js +++ b/packages/js/src/dashboard/scores/components/site-kit-table.js @@ -33,7 +33,7 @@ const SortableHeader = ( { columnName, isAscending, onClick, index } ) => { * @returns {JSX.Element} The element. */ const ScoreBullet = ( { score } ) => ( - + { SCORE_META[ score ].label } From d05d22cc69cd05ecc448154b5384ef4d5725b387 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Tue, 7 Jan 2025 16:42:42 +0200 Subject: [PATCH 28/55] remove classes and align bullet to the end center --- css/src/general-page.css | 14 ++++++++----- .../scores/components/site-kit-table.js | 20 ++++++++++--------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/css/src/general-page.css b/css/src/general-page.css index 2319634e193..21a04934884 100644 --- a/css/src/general-page.css +++ b/css/src/general-page.css @@ -249,28 +249,32 @@ body.toplevel_page_wpseo_dashboard { } - .yst-site-kit-widget-table .yst-numbered-cell:first-child { + .yst-site-kit-widget-table td:first-child { counter-increment: row-number; @apply yst-relative; } - .yst-site-kit-widget-table .yst-numbered-cell:first-child span { + .yst-site-kit-widget-table td:first-child div { @apply yst-text-slate-900 yst-font-medium; } - .yst-site-kit-widget-table .yst-numbered-cell:first-child span, .yst-site-kit-widget-table th:first-child span{ + .yst-site-kit-widget-table td:first-child div, .yst-site-kit-widget-table th:first-child div{ @apply yst-ps-3; } - .yst-site-kit-widget-table .yst-numbered-cell:first-child::before { + .yst-site-kit-widget-table td:first-child::before { content: counter(row-number) ". "; @apply yst-absolute yst-left-0 yst-text-slate-500 yst-font-normal; } /* Initialize the counter at the parent element level */ - .yst-parent-numbered-cell { + .yst-site-kit-widget-table tbody { counter-reset: row-number; } + .yst-site-kit-widget-table th:last-child div { + @apply yst-text-end; + } + /* RTL */ &.rtl { diff --git a/packages/js/src/dashboard/scores/components/site-kit-table.js b/packages/js/src/dashboard/scores/components/site-kit-table.js index 92b152c7c80..16de054edc9 100644 --- a/packages/js/src/dashboard/scores/components/site-kit-table.js +++ b/packages/js/src/dashboard/scores/components/site-kit-table.js @@ -33,11 +33,13 @@ const SortableHeader = ( { columnName, isAscending, onClick, index } ) => { * @returns {JSX.Element} The element. */ const ScoreBullet = ( { score } ) => ( - - - { SCORE_META[ score ].label } - - +
+
+ + { SCORE_META[ score ].label } + +
+
); /** @@ -95,20 +97,20 @@ export const SiteKitTable = ( { title, columns, data } ) => { index={ index } onClick={ handleSort } /> - ) : { column.label } } + ) :
{ column.label }
} ) } - + { sortedData.map( ( row, rowIndex ) => ( { row.map( ( cell, cellIndex ) => ( - { columns[ cellIndex ].name === "seo-score" ? : :
{ cell } } + >{ cell }
}
) ) }
) ) } From 70939feb2544ad25f90a110c2ce1a7b9c6e1f0ac Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 10 Jan 2025 16:58:51 +0200 Subject: [PATCH 29/55] refactor dashboard table --- css/src/general-page.css | 27 -------- .../dashboard-table.js} | 9 ++- .../src/dashboard/scores/components/scores.js | 64 +++++++++---------- .../dashboard-table.test.js} | 14 ++-- 4 files changed, 44 insertions(+), 70 deletions(-) rename packages/js/src/dashboard/{scores/components/site-kit-table.js => components/dashboard-table.js} (91%) rename packages/js/tests/dashboard/{scores/components/site-kit-table.test.js => components/dashboard-table.test.js} (77%) diff --git a/css/src/general-page.css b/css/src/general-page.css index 21a04934884..d134b01a032 100644 --- a/css/src/general-page.css +++ b/css/src/general-page.css @@ -248,33 +248,6 @@ body.toplevel_page_wpseo_dashboard { } } - - .yst-site-kit-widget-table td:first-child { - counter-increment: row-number; - @apply yst-relative; - } - - .yst-site-kit-widget-table td:first-child div { - @apply yst-text-slate-900 yst-font-medium; - } - .yst-site-kit-widget-table td:first-child div, .yst-site-kit-widget-table th:first-child div{ - @apply yst-ps-3; - } - - .yst-site-kit-widget-table td:first-child::before { - content: counter(row-number) ". "; - @apply yst-absolute yst-left-0 yst-text-slate-500 yst-font-normal; - } - - /* Initialize the counter at the parent element level */ - .yst-site-kit-widget-table tbody { - counter-reset: row-number; - } - - .yst-site-kit-widget-table th:last-child div { - @apply yst-text-end; - } - /* RTL */ &.rtl { diff --git a/packages/js/src/dashboard/scores/components/site-kit-table.js b/packages/js/src/dashboard/components/dashboard-table.js similarity index 91% rename from packages/js/src/dashboard/scores/components/site-kit-table.js rename to packages/js/src/dashboard/components/dashboard-table.js index 16de054edc9..e1a37280fc3 100644 --- a/packages/js/src/dashboard/scores/components/site-kit-table.js +++ b/packages/js/src/dashboard/components/dashboard-table.js @@ -1,7 +1,7 @@ import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/solid"; import { useState, useCallback } from "@wordpress/element"; import classNames from "classnames"; -import { SCORE_META } from "../score-meta"; +import { SCORE_META } from "../scores/score-meta"; import { Paper, Table, Title } from "@yoast/ui-library"; /** @@ -51,7 +51,7 @@ const ScoreBullet = ( { score } ) => ( * * @returns {JSX.Element} The element. */ -export const SiteKitTable = ( { title, columns, data } ) => { +export const DashboardTable = ( { title, columns, data } ) => { const [ sortConfig, setSortConfig ] = useState( { key: null, ascending: true } ); const sortedData = [ ...data ].sort( ( a, b ) => { @@ -78,13 +78,14 @@ export const SiteKitTable = ( { title, columns, data } ) => { }, [ sortConfig, setSortConfig ] ); return ( - + { title }
+ { "" } { columns.map( ( column, index ) => { { sortedData.map( ( row, rowIndex ) => ( + { rowIndex + 1 }. { row.map( ( cell, cellIndex ) => ( { columns[ cellIndex ].name === "seo-score" ? :
{ cell }
}
) ) } diff --git a/packages/js/src/dashboard/scores/components/scores.js b/packages/js/src/dashboard/scores/components/scores.js index fc986e02d20..a2d0917a3ad 100644 --- a/packages/js/src/dashboard/scores/components/scores.js +++ b/packages/js/src/dashboard/scores/components/scores.js @@ -107,41 +107,39 @@ export const Scores = ( { analysisType, contentTypes, endpoint, headers } ) => { }, [ selectedContentType.name ] ); return ( - -
- - { analysisType === "readability" - ? __( "Readability scores", "wordpress-seo" ) - : __( "SEO scores", "wordpress-seo" ) - } - -
- + + { analysisType === "readability" + ? __( "Readability scores", "wordpress-seo" ) + : __( "SEO scores", "wordpress-seo" ) + } + +
+ + { selectedContentType.taxonomy && selectedContentType.taxonomy?.links?.search && + - { selectedContentType.taxonomy && selectedContentType.taxonomy?.links?.search && - - } -
-
- - { ! error && ( - - ) } -
+ } +
+
+ + { ! error && ( + + ) }
); diff --git a/packages/js/tests/dashboard/scores/components/site-kit-table.test.js b/packages/js/tests/dashboard/components/dashboard-table.test.js similarity index 77% rename from packages/js/tests/dashboard/scores/components/site-kit-table.test.js rename to packages/js/tests/dashboard/components/dashboard-table.test.js index d429223c8b1..6cb144cd598 100644 --- a/packages/js/tests/dashboard/scores/components/site-kit-table.test.js +++ b/packages/js/tests/dashboard/components/dashboard-table.test.js @@ -1,9 +1,9 @@ -import { render, screen, fireEvent } from "@testing-library/react"; +import { render, screen, fireEvent } from "../../test-utils.js"; import "@testing-library/jest-dom"; -import { SiteKitTable } from "../../../../src/dashboard/scores/components/site-kit-table.js"; -import { SCORE_META } from "../../../../src/dashboard/scores/score-meta.js"; +import { DashboardTable } from "../../../src/dashboard/components/dashboard-table.js"; +import { SCORE_META } from "../../../src/dashboard/scores/score-meta.js"; -describe( "SiteKitTable", () => { +describe( "DashboardTable", () => { const columns = [ { name: "landing-page", label: "Landing page" }, { name: "clicks", label: "Clicks", sortable: true }, @@ -22,7 +22,7 @@ describe( "SiteKitTable", () => { ]; it( "renders the table with correct title and columns", () => { - render( ); + render( ); expect( screen.getByText( "Test Table" ) ).toBeInTheDocument(); columns.forEach( ( column ) => { expect( screen.getByText( column.label ) ).toBeInTheDocument(); @@ -30,7 +30,7 @@ describe( "SiteKitTable", () => { } ); it( "renders the table rows correctly", () => { - render( ); + render( ); data.forEach( ( row ) => { const rowElement = screen.getByText( row[ 0 ] ).closest( "tr" ); expect( rowElement ).toHaveTextContent( row[ 0 ] ); @@ -43,7 +43,7 @@ describe( "SiteKitTable", () => { } ); it( "sorts the table correctly when a sortable column header is clicked", () => { - render( ); + render( ); const clicksHeader = screen.getByText( "Clicks" ); fireEvent.click( clicksHeader ); const sortedData = data.sort( ( a, b ) => a[ 1 ] - b[ 1 ] ); From dc14803cd3ccecc5982bccff704b09f428fa0055 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 10 Jan 2025 17:04:41 +0200 Subject: [PATCH 30/55] revert dashboard changes revert dashboard changes --- .../js/src/dashboard/components/dashboard.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/js/src/dashboard/components/dashboard.js b/packages/js/src/dashboard/components/dashboard.js index a62768a1833..62f58e02edc 100644 --- a/packages/js/src/dashboard/components/dashboard.js +++ b/packages/js/src/dashboard/components/dashboard.js @@ -21,15 +21,13 @@ export const Dashboard = ( { contentTypes, userName, features, endpoints, header return ( <> -
-
- { features.indexables && features.seoAnalysis && ( - - ) } - { features.indexables && features.readabilityAnalysis && ( - - ) } -
+
+ { features.indexables && features.seoAnalysis && ( + + ) } + { features.indexables && features.readabilityAnalysis && ( + + ) }
); From 827f5b7a63261e9a7297c5c4527ede2d02fac9c8 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 13 Jan 2025 10:50:09 +0200 Subject: [PATCH 31/55] add type for columns and data, fix table overflow --- .../dashboard/components/dashboard-table.js | 85 ++++++++++--------- packages/js/src/dashboard/index.js | 14 +++ 2 files changed, 60 insertions(+), 39 deletions(-) diff --git a/packages/js/src/dashboard/components/dashboard-table.js b/packages/js/src/dashboard/components/dashboard-table.js index e1a37280fc3..8efc04825cd 100644 --- a/packages/js/src/dashboard/components/dashboard-table.js +++ b/packages/js/src/dashboard/components/dashboard-table.js @@ -4,6 +4,11 @@ import classNames from "classnames"; import { SCORE_META } from "../scores/score-meta"; import { Paper, Table, Title } from "@yoast/ui-library"; +/** + * @type {import("../index").Column} Column + * @type {import("../index").Data} Data + */ + /** * The sortable header component. * @@ -20,7 +25,7 @@ const SortableHeader = ( { columnName, isAscending, onClick, index } ) => { const ChevronIcon = isAscending ? ChevronDownIcon : ChevronUpIcon; - return ; @@ -46,8 +51,8 @@ const ScoreBullet = ( { score } ) => ( * The Site Kit table component. * * @param {string} title The table title. - * @param {[object]} columns The columns properties. - * @param {[object]} data The rows data. + * @param {Column[]} columns The columns properties. + * @param {Data} data The rows data. * * @returns {JSX.Element} The element. */ @@ -82,43 +87,45 @@ export const DashboardTable = ( { title, columns, data } ) => { { title } -
- - - { "" } - { columns.map( ( column, index ) => - - { column.sortable ? ( - - ) :
{ column.label }
} -
- ) } -
-
- - { sortedData.map( ( row, rowIndex ) => ( - - { rowIndex + 1 }. - { row.map( ( cell, cellIndex ) => ( - - { columns[ cellIndex ].name === "seo-score" ? :
{ cell }
} -
) ) } +
+
+ + + { "" } + { columns.map( ( column, index ) => + + { column.sortable ? ( + + ) :
{ column.label }
} +
+ ) }
- ) ) } - -
+ + + { sortedData.map( ( row, rowIndex ) => ( + + { rowIndex + 1 }. + { row.map( ( cell, cellIndex ) => ( + + { columns[ cellIndex ].name === "seo-score" ? :
{ cell }
} +
) ) } +
+ ) ) } +
+ +
); }; diff --git a/packages/js/src/dashboard/index.js b/packages/js/src/dashboard/index.js index 2c9b4ad479c..675c5bf5a83 100644 --- a/packages/js/src/dashboard/index.js +++ b/packages/js/src/dashboard/index.js @@ -54,3 +54,17 @@ export { Dashboard } from "./components/dashboard"; * @typedef {Object} Links The links. * @property {string} dashboardLearnMore The dashboard information link. */ + +/** + * @typedef {Object} Column A column in the table. + * @property {string} name The unique identifier. + * @property {string} label The user-facing label. + * @property {boolean} sortable Whether the column is sortable. + * @property {string} [className] The CSS class name for the column. + */ + +/** + * @typedef {Array>} Data The data for the table rows. + */ + + From 8bfa47c191f2ec431766b59a6daa5fc603d01b98 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 15 Jan 2025 12:28:22 +0200 Subject: [PATCH 32/55] remove sortable column --- .../dashboard/components/dashboard-table.js | 67 ++----------------- 1 file changed, 5 insertions(+), 62 deletions(-) diff --git a/packages/js/src/dashboard/components/dashboard-table.js b/packages/js/src/dashboard/components/dashboard-table.js index 8efc04825cd..81a1020f40e 100644 --- a/packages/js/src/dashboard/components/dashboard-table.js +++ b/packages/js/src/dashboard/components/dashboard-table.js @@ -1,36 +1,12 @@ -import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/solid"; -import { useState, useCallback } from "@wordpress/element"; import classNames from "classnames"; -import { SCORE_META } from "../scores/score-meta"; import { Paper, Table, Title } from "@yoast/ui-library"; +import { SCORE_META } from "../scores/score-meta"; /** * @type {import("../index").Column} Column * @type {import("../index").Data} Data */ -/** - * The sortable header component. - * - * @param {string} columnName The column name. - * @param {boolean} isAscending Whether the sorting is ascending. - * @param {Function} onClick The click handler. - * @param {number} index The index of the column. - * @returns {JSX.Element} The element. - */ -const SortableHeader = ( { columnName, isAscending, onClick, index } ) => { - const handleSort = useCallback( () => { - onClick( index ); - }, [ onClick, index ] ); - - const ChevronIcon = isAscending ? ChevronDownIcon : ChevronUpIcon; - - return ; -}; - /** * The score bullet component. * @@ -57,31 +33,6 @@ const ScoreBullet = ( { score } ) => ( * @returns {JSX.Element} The element. */ export const DashboardTable = ( { title, columns, data } ) => { - const [ sortConfig, setSortConfig ] = useState( { key: null, ascending: true } ); - - const sortedData = [ ...data ].sort( ( a, b ) => { - if ( sortConfig.key === null ) { - return 0; - } - const aValue = a[ sortConfig.key ]; - const bValue = b[ sortConfig.key ]; - if ( aValue < bValue ) { - return sortConfig.ascending ? -1 : 1; - } - if ( aValue > bValue ) { - return sortConfig.ascending ? 1 : -1; - } - return 0; - } ); - - const handleSort = useCallback( ( columnIndex ) => { - let ascending = true; - if ( sortConfig.key === columnIndex && sortConfig.ascending ) { - ascending = false; - } - setSortConfig( { key: columnIndex, ascending } ); - }, [ sortConfig, setSortConfig ] ); - return ( @@ -92,32 +43,24 @@ export const DashboardTable = ( { title, columns, data } ) => { <Table.Head className="yst-bg-white yst-mt-2 yst-border-b-slate-300 yst-border-b yst-border-t-0"> <Table.Row> <Table.Header>{ "" }</Table.Header> - { columns.map( ( column, index ) => + { columns.map( ( column ) => <Table.Header key={ `${column.name}-${title}` } className="yst-align-bottom yst-pb-3 yst-text-slate-900 yst-font-medium" > - { column.sortable ? ( - <SortableHeader - columnName={ column.label } - isAscending={ sortConfig.ascending } - index={ index } - onClick={ handleSort } - /> - ) : <div className={ column.className }>{ column.label }</div> } + <div className={ column.className }>{ column.label }</div> </Table.Header> ) } </Table.Row> </Table.Head> <Table.Body> - { sortedData.map( ( row, rowIndex ) => ( + { data.map( ( row, rowIndex ) => ( <Table.Row key={ `row-${title}-${rowIndex}` }> <Table.Cell>{ rowIndex + 1 }. </Table.Cell> { row.map( ( cell, cellIndex ) => ( <Table.Cell key={ `cell-${title}-${cellIndex}` } className="yst-numbered-cell"> { columns[ cellIndex ].name === "seo-score" ? <ScoreBullet score={ cell } /> : <div - className={ classNames( columns[ cellIndex ].sortable ? "yst-text-end yst-pe-5" : "", - cellIndex === 0 ? "yst-text-slate-900 yst-font-medium" : "", + className={ classNames( cellIndex === 0 ? "yst-text-slate-900 yst-font-medium" : "", columns[ cellIndex ].className ) } >{ cell }</div> } </Table.Cell> ) ) } From 9169efb2fadff8480b01b4689c09295b61e94033 Mon Sep 17 00:00:00 2001 From: Vraja Das <vraja.pro@gmail.com> Date: Wed, 15 Jan 2025 16:54:57 +0200 Subject: [PATCH 33/55] add minimal variant to ui library and update storybook --- .../src/elements/table/docs/index.js | 1 + .../src/elements/table/docs/table-row.md | 2 +- .../table/docs/table-variant-minimal.md | 1 + .../ui-library/src/elements/table/index.js | 22 ++++++--- .../ui-library/src/elements/table/stories.js | 38 +++++++++++++++- .../ui-library/src/elements/table/style.css | 45 ++++++++++++++++--- 6 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 packages/ui-library/src/elements/table/docs/table-variant-minimal.md diff --git a/packages/ui-library/src/elements/table/docs/index.js b/packages/ui-library/src/elements/table/docs/index.js index b930f54b03d..9c56c7deef8 100644 --- a/packages/ui-library/src/elements/table/docs/index.js +++ b/packages/ui-library/src/elements/table/docs/index.js @@ -4,3 +4,4 @@ export { default as tableCell } from "./table-cell.md"; export { default as tableHead } from "./table-head.md"; export { default as tableHeader } from "./table-header.md"; export { default as tableRow } from "./table-row.md"; +export { default as minimal } from "./table-variant-minimal.md"; diff --git a/packages/ui-library/src/elements/table/docs/table-row.md b/packages/ui-library/src/elements/table/docs/table-row.md index 4f489ab8eb7..eb90b5eb9cc 100644 --- a/packages/ui-library/src/elements/table/docs/table-row.md +++ b/packages/ui-library/src/elements/table/docs/table-row.md @@ -1 +1 @@ -The sub component `Table.Row`. +The sub component `Table.Row`. The row has a variant props which can be set to `striped`. diff --git a/packages/ui-library/src/elements/table/docs/table-variant-minimal.md b/packages/ui-library/src/elements/table/docs/table-variant-minimal.md new file mode 100644 index 00000000000..b9244731d56 --- /dev/null +++ b/packages/ui-library/src/elements/table/docs/table-variant-minimal.md @@ -0,0 +1 @@ +The minial variant is the table without the table border. \ No newline at end of file diff --git a/packages/ui-library/src/elements/table/index.js b/packages/ui-library/src/elements/table/index.js index bb13ef9ebc1..d22c317f45b 100644 --- a/packages/ui-library/src/elements/table/index.js +++ b/packages/ui-library/src/elements/table/index.js @@ -16,7 +16,7 @@ const rowClassNameMap = { * @returns {JSX.Element} The element. */ const Cell = ( { children, className = "", ...props } ) => ( - <td className={ classNames( "yst-table-cell yst-px-3 yst-py-4 yst-text-sm yst-text-slate-600", className ) } { ...props }> + <td className={ classNames( "yst-table-cell", className ) } { ...props }> { children } </td> ); @@ -54,7 +54,7 @@ Row.propTypes = { */ const Header = ( { children, className = "", ...props } ) => ( <th - className={ classNames( "yst-table-header yst-px-3 yst-py-4 yst-text-start yst-text-sm yst-font-semibold yst-text-slate-900", className ) } + className={ classNames( "yst-table-header", className ) } { ...props } > { children } @@ -73,7 +73,7 @@ Header.propTypes = { * @returns {JSX.Element} The element. */ const Head = ( { children, className = "", ...props } ) => ( - <thead className={ classNames( "yst-bg-slate-50", className ) } { ...props }>{ children }</thead> + <thead className={ className } { ...props }>{ children }</thead> ); Head.propTypes = { @@ -88,7 +88,7 @@ Head.propTypes = { * @returns {JSX.Element} The element. */ const Body = ( { children, className = "", ...props } ) => ( - <tbody className={ classNames( "yst-divide-y yst-divide-gray-200 yst-bg-white", className ) } { ...props }>{ children }</tbody> + <tbody className={ className } { ...props }>{ children }</tbody> ); Body.propTypes = { @@ -96,15 +96,21 @@ Body.propTypes = { className: PropTypes.string, }; +const tableVariants = { + "default": "yst-table--default", + minimal: "yst-table--minimal", +}; + /** * @param {JSX.node} children The content. * @param {string} [className] Optional class name. + * @param {string} [variant] The variant of the table. * @param {Object} [props] Optional table props. * @returns {JSX.Element} The element. */ -const Table = forwardRef( ( { children, className = "", ...props }, ref ) => ( - <div className="yst-table-wrapper yst-shadow yst-ring-1 yst-ring-black yst-ring-opacity-5"> - <table className={ classNames( "yst-min-w-full yst-divide-y yst-divide-slate-300", className ) } { ...props } ref={ ref }> +const Table = forwardRef( ( { children, className = "", variant = "default", ...props }, ref ) => ( + <div className={ classNames( "yst-table-wrapper", tableVariants[ variant ] ) }> + <table className={ className } { ...props } ref={ ref }> { children } </table> </div> @@ -114,9 +120,11 @@ Table.displayName = "Table"; Table.propTypes = { children: PropTypes.node.isRequired, className: PropTypes.string, + variant: PropTypes.string, }; Table.defaultProps = { className: "", + variant: "default", }; Table.Head = Head; diff --git a/packages/ui-library/src/elements/table/stories.js b/packages/ui-library/src/elements/table/stories.js index 021167d1e76..bbc6e679338 100644 --- a/packages/ui-library/src/elements/table/stories.js +++ b/packages/ui-library/src/elements/table/stories.js @@ -1,7 +1,7 @@ import React from "react"; import Table from "."; import { InteractiveDocsPage } from "../../../.storybook/interactive-docs-page"; -import { component, tableBody, tableCell, tableHead, tableHeader, tableRow } from "./docs"; +import { component, tableBody, tableCell, tableHead, tableHeader, tableRow, minimal } from "./docs"; export const Factory = { parameters: { @@ -199,6 +199,40 @@ export const TableCell = { }, }; +export const MinimalVariant = { + name: "Table variant minimal", + parameters: { + controls: { disable: false }, + docs: { description: { story: minimal } }, + }, + args: { + variant: "minimal", + children: ( + <> + <Table.Head> + <Table.Row> + <Table.Header>Header 1</Table.Header> + <Table.Header>Header 2</Table.Header> + <Table.Header>Header 3</Table.Header> + </Table.Row> + </Table.Head> + <Table.Body> + <Table.Row> + <Table.Cell>Cell 1</Table.Cell> + <Table.Cell>Cell 2</Table.Cell> + <Table.Cell>Cell 3</Table.Cell> + </Table.Row> + <Table.Row> + <Table.Cell>Cell 1</Table.Cell> + <Table.Cell>Cell 2</Table.Cell> + <Table.Cell>Cell 3</Table.Cell> + </Table.Row> + </Table.Body> + </> + ), + }, +}; + export default { title: "1) Elements/Table", component: Table, @@ -208,7 +242,7 @@ export default { parameters: { docs: { description: { component }, - page: () => <InteractiveDocsPage stories={ [ TableHead, TableRow, TableHeader, TableBody, TableCell ] } />, + page: () => <InteractiveDocsPage stories={ [ TableHead, TableRow, TableHeader, TableBody, TableCell, MinimalVariant ] } />, }, }, }; diff --git a/packages/ui-library/src/elements/table/style.css b/packages/ui-library/src/elements/table/style.css index 1885ffbaf45..9a6a86b0a5b 100644 --- a/packages/ui-library/src/elements/table/style.css +++ b/packages/ui-library/src/elements/table/style.css @@ -1,15 +1,50 @@ @layer components { .yst-root { .yst-table-wrapper { - @apply yst-rounded-lg; + table { + @apply yst-min-w-full; + + .yst-table-header { + @apply yst-px-3 yst-py-4 yst-text-start yst-text-sm yst-font-semibold yst-text-slate-900; + } + + .yst-table-cell { + @apply yst-px-3 yst-py-4 yst-text-sm yst-text-slate-600; + } + } } + + .yst-table--default { + @apply yst-rounded-lg yst-shadow yst-ring-1 yst-ring-black yst-ring-opacity-5; + + table { + @apply yst-divide-y yst-divide-slate-300; + + thead{ + @apply yst-bg-slate-50; + + .yst-table-header { + @apply first:yst-rounded-ss-lg last:yst-rounded-se-lg; + } + } + + tbody { + @apply yst-divide-y yst-divide-gray-200 yst-bg-white; + } - .yst-table-header { - @apply first:yst-rounded-ss-lg last:yst-rounded-se-lg; + .yst-table-row:last-of-type .yst-table-cell { + @apply first:yst-rounded-es-lg last:yst-rounded-ee-lg; + } + } } - .yst-table-row:last-of-type .yst-table-cell { - @apply first:yst-rounded-es-lg last:yst-rounded-ee-lg; + .yst-table--minimal { + table { + @apply yst-divide-y yst-divide-slate-300; + tbody { + @apply yst-divide-y yst-divide-gray-200 yst-bg-white; + } + } } } } From 87a9e20d8242f4dfb6f4c7332571ffd730c11232 Mon Sep 17 00:00:00 2001 From: Vraja Das <vraja.pro@gmail.com> Date: Wed, 15 Jan 2025 17:14:37 +0200 Subject: [PATCH 34/55] drop sorting drop sort drop sorting from tests --- .../js/src/dashboard/components/dashboard-table.js | 4 ++-- .../dashboard/components/dashboard-table.test.js | 13 ++----------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/js/src/dashboard/components/dashboard-table.js b/packages/js/src/dashboard/components/dashboard-table.js index 81a1020f40e..2e609dba78c 100644 --- a/packages/js/src/dashboard/components/dashboard-table.js +++ b/packages/js/src/dashboard/components/dashboard-table.js @@ -39,7 +39,7 @@ export const DashboardTable = ( { title, columns, data } ) => { { title }
- +
{ "" } @@ -67,7 +67,7 @@ export const DashboardTable = ( { title, columns, data } ) => { ) ) } -
+
); diff --git a/packages/js/tests/dashboard/components/dashboard-table.test.js b/packages/js/tests/dashboard/components/dashboard-table.test.js index 6cb144cd598..a39abad6d2e 100644 --- a/packages/js/tests/dashboard/components/dashboard-table.test.js +++ b/packages/js/tests/dashboard/components/dashboard-table.test.js @@ -1,4 +1,4 @@ -import { render, screen, fireEvent } from "../../test-utils.js"; +import { render, screen } from "../../test-utils.js"; import "@testing-library/jest-dom"; import { DashboardTable } from "../../../src/dashboard/components/dashboard-table.js"; import { SCORE_META } from "../../../src/dashboard/scores/score-meta.js"; @@ -6,7 +6,7 @@ import { SCORE_META } from "../../../src/dashboard/scores/score-meta.js"; describe( "DashboardTable", () => { const columns = [ { name: "landing-page", label: "Landing page" }, - { name: "clicks", label: "Clicks", sortable: true }, + { name: "clicks", label: "Clicks" }, { name: "impressions", label: "Impressions" }, { name: "ctr", label: "CTR" }, { name: "average-position", label: "Average position" }, @@ -41,13 +41,4 @@ describe( "DashboardTable", () => { expect( rowElement ).toHaveTextContent( SCORE_META[ row[ 5 ] ].label ); } ); } ); - - it( "sorts the table correctly when a sortable column header is clicked", () => { - render( ); - const clicksHeader = screen.getByText( "Clicks" ); - fireEvent.click( clicksHeader ); - const sortedData = data.sort( ( a, b ) => a[ 1 ] - b[ 1 ] ); - const firstRow = screen.getAllByRole( "row" )[ 1 ]; - expect( firstRow ).toHaveTextContent( sortedData[ 0 ][ 0 ] ); - } ); } ); From 5dd465b92895c3d0b2385e45e0bde9b4f971ee87 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Tue, 21 Jan 2025 14:54:01 +0100 Subject: [PATCH 35/55] Use needed conditional. --- .../search-rankings/abstract-ranking-route.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php index 0f83c271885..617876469ec 100644 --- a/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php +++ b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php @@ -6,7 +6,7 @@ use WP_REST_Request; use WP_REST_Response; use WPSEO_Capability_Utils; -use Yoast\WP\SEO\Conditionals\No_Conditionals; +use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional; use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Search_Rankings_Data_Provider; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Search_Console_Parameters; use Yoast\WP\SEO\Main; @@ -17,8 +17,6 @@ */ abstract class Abstract_Ranking_Route implements Route_Interface { - use No_Conditionals; - /** * The namespace of the rout. * @@ -46,6 +44,15 @@ abstract class Abstract_Ranking_Route implements Route_Interface { */ private $search_rankings_data_provider; + /** + * Returns the needed conditionals. + * + * @return array The conditionals that must be met to load this. + */ + public static function get_conditionals(): array { + return [ Google_Site_Kit_Feature_Conditional::class ]; + } + /** * The constructor. * @@ -99,7 +106,7 @@ public function register_routes() { [ 'methods' => 'GET', 'callback' => [ $this, 'get_rankings' ], - 'permission_callback' => [ $this, 'permission_manage_options' ], + //'permission_callback' => [ $this, 'permission_manage_options' ], 'args' => [ 'limit' => [ 'required' => true, From e2b3ef33080e6d945ae0ec2e05d12fbe2622f9ab Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Tue, 21 Jan 2025 16:29:40 +0100 Subject: [PATCH 36/55] Improvements of api response and dates. --- .../domain/search-rankings/search-data.php | 23 +++++++++---------- .../search-rankings-parser.php | 2 +- .../abstract-ranking-route.php | 16 ++++++++----- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/dashboard/domain/search-rankings/search-data.php b/src/dashboard/domain/search-rankings/search-data.php index 2a72a093c98..46e0136e4cf 100644 --- a/src/dashboard/domain/search-rankings/search-data.php +++ b/src/dashboard/domain/search-rankings/search-data.php @@ -38,12 +38,11 @@ class Search_Data implements Data_Interface { private $position; /** - * An array representation of the different `keys`. - * In the context of this domain object keys can represent a `URI` or a `search term` + * In the context of this domain object subject can represent a `URI` or a `search term` * - * @var string[] + * @var string */ - private $keys = []; + private $subject; /** * The seo score. @@ -55,18 +54,18 @@ class Search_Data implements Data_Interface { /** * The constructor. * - * @param int $clicks The clicks. - * @param float $ctr The ctr. - * @param int $impressions The impressions. - * @param float $position The position. - * @param string[] $keys The clicks. + * @param int $clicks The clicks. + * @param float $ctr The ctr. + * @param int $impressions The impressions. + * @param float $position The position. + * @param string $subject The subject of the data. */ - public function __construct( int $clicks, float $ctr, int $impressions, float $position, array $keys ) { + public function __construct( int $clicks, float $ctr, int $impressions, float $position, string $subject ) { $this->clicks = $clicks; $this->ctr = $ctr; $this->impressions = $impressions; $this->position = $position; - $this->keys = $keys; + $this->subject = $subject; $this->seo_score = 0; } @@ -81,7 +80,7 @@ public function to_array(): array { 'ctr' => $this->ctr, 'impressions' => $this->impressions, 'position' => $this->position, - 'keys' => $this->keys, + 'subject' => $this->subject, 'seoScore' => $this->seo_score, ]; } diff --git a/src/dashboard/infrastructure/search-rankings/search-rankings-parser.php b/src/dashboard/infrastructure/search-rankings/search-rankings-parser.php index 13f5f9116fa..dc4e1246ead 100644 --- a/src/dashboard/infrastructure/search-rankings/search-rankings-parser.php +++ b/src/dashboard/infrastructure/search-rankings/search-rankings-parser.php @@ -22,7 +22,7 @@ class Search_Rankings_Parser { public function parse( array $results ): Data_Container { $search_data_container = new Data_Container(); foreach ( $results as $ranking ) { - $search_data_container->add_data( new Search_Data( $ranking->clicks, $ranking->ctr, $ranking->impressions, $ranking->position, $ranking->keys ) ); + $search_data_container->add_data( new Search_Data( $ranking->clicks, $ranking->ctr, $ranking->impressions, $ranking->position, $ranking->keys[0] ) ); } return $search_data_container; diff --git a/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php index 617876469ec..fe8569202b3 100644 --- a/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php +++ b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php @@ -2,6 +2,8 @@ // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Search_Rankings; +use DateTime; +use DateTimeZone; use Exception; use WP_REST_Request; use WP_REST_Response; @@ -47,7 +49,7 @@ abstract class Abstract_Ranking_Route implements Route_Interface { /** * Returns the needed conditionals. * - * @return array The conditionals that must be met to load this. + * @return array The conditionals that must be met to load this. */ public static function get_conditionals(): array { return [ Google_Site_Kit_Feature_Conditional::class ]; @@ -78,9 +80,8 @@ public function set_request_parameters( /** * Returns the route prefix. * - * @return string The route prefix. - * * @throws Exception If the ROUTE_PREFIX constant is not set in the child class. + * @return string The route prefix. */ public static function get_route_prefix() { $class = static::class; @@ -106,7 +107,7 @@ public function register_routes() { [ 'methods' => 'GET', 'callback' => [ $this, 'get_rankings' ], - //'permission_callback' => [ $this, 'permission_manage_options' ], + 'permission_callback' => [ $this, 'permission_manage_options' ], 'args' => [ 'limit' => [ 'required' => true, @@ -131,8 +132,11 @@ public function register_routes() { public function get_rankings( WP_REST_Request $request ): WP_REST_Response { try { $this->request_parameters->set_limit( $request->get_param( 'limit' ) ); - $this->request_parameters->set_start_date( '2024-01-01' ); - $this->request_parameters->set_end_date( '2025-01-01' ); + $date = new DateTime( 'now', new DateTimeZone( 'UTC' ) ); + $date->modify( '-28 days' ); + + $this->request_parameters->set_start_date( $date->format( 'Y-m-d' ) ); + $this->request_parameters->set_end_date( ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->format( 'Y-m-d' ) ); $search_data_container = $this->search_rankings_data_provider->get_data( $this->request_parameters ); From ef5a2ed80a44347e705e191ee3a65ab75ad83527 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Tue, 21 Jan 2025 18:10:16 +0200 Subject: [PATCH 37/55] add reusable widget table --- .../dashboard/components/dashboard-table.js | 74 ----------------- .../components/most-popular-table.js | 42 ++++++++++ .../src/dashboard/components/table-widget.js | 81 +++++++++++++++++++ packages/js/src/dashboard/index.js | 18 ++--- .../components/dashboard-table.test.js | 44 ---------- .../ui-library/src/elements/table/style.css | 4 + 6 files changed, 134 insertions(+), 129 deletions(-) delete mode 100644 packages/js/src/dashboard/components/dashboard-table.js create mode 100644 packages/js/src/dashboard/components/most-popular-table.js create mode 100644 packages/js/src/dashboard/components/table-widget.js delete mode 100644 packages/js/tests/dashboard/components/dashboard-table.test.js diff --git a/packages/js/src/dashboard/components/dashboard-table.js b/packages/js/src/dashboard/components/dashboard-table.js deleted file mode 100644 index 2e609dba78c..00000000000 --- a/packages/js/src/dashboard/components/dashboard-table.js +++ /dev/null @@ -1,74 +0,0 @@ -import classNames from "classnames"; -import { Paper, Table, Title } from "@yoast/ui-library"; -import { SCORE_META } from "../scores/score-meta"; - -/** - * @type {import("../index").Column} Column - * @type {import("../index").Data} Data - */ - -/** - * The score bullet component. - * - * @param {string} score The score. - * @returns {JSX.Element} The element. - */ -const ScoreBullet = ( { score } ) => ( -
-
- - { SCORE_META[ score ].label } - -
-
-); - -/** - * The Site Kit table component. - * - * @param {string} title The table title. - * @param {Column[]} columns The columns properties. - * @param {Data} data The rows data. - * - * @returns {JSX.Element} The element. - */ -export const DashboardTable = ( { title, columns, data } ) => { - return ( - - - { title } - -
- - - - { "" } - { columns.map( ( column ) => - -
{ column.label }
-
- ) } -
-
- - { data.map( ( row, rowIndex ) => ( - - { rowIndex + 1 }. - { row.map( ( cell, cellIndex ) => ( - - { columns[ cellIndex ].name === "seo-score" ? :
{ cell }
} -
) ) } -
- ) ) } -
-
-
-
- ); -}; diff --git a/packages/js/src/dashboard/components/most-popular-table.js b/packages/js/src/dashboard/components/most-popular-table.js new file mode 100644 index 00000000000..2222009e391 --- /dev/null +++ b/packages/js/src/dashboard/components/most-popular-table.js @@ -0,0 +1,42 @@ +import { __ } from "@wordpress/i18n"; +import { TableWidget } from "./table-widget"; + +/** + * @type {import("../index").MostPopularContent} Most popular content + */ + +/** + * The top 5 most popular content table component. + * + * @param {[MostPopularContent]} Data The component props. + * + * @returns {JSX.Element} The element. + */ +export const MostPopularTable = ( { data } ) => { + return + + { __( "Landing page", "wordpress-seo" ) } + { __( "Clicks", "wordpress-seo" ) } + { __( "Impressions", "wordpress-seo" ) } + { __( "CTR", "wordpress-seo" ) } + { __( "Average position", "wordpress-seo" ) } + +
+ { __( "SEO score", "wordpress-seo" ) } +
+
+
+ + { data.map( ( { subject, clicks, impressions, ctr, averagePosition, seoScore }, index ) => ( + + { subject } + { clicks } + { impressions } + { ctr } + { averagePosition } + + + ) ) } + +
; +}; diff --git a/packages/js/src/dashboard/components/table-widget.js b/packages/js/src/dashboard/components/table-widget.js new file mode 100644 index 00000000000..ea11ac99653 --- /dev/null +++ b/packages/js/src/dashboard/components/table-widget.js @@ -0,0 +1,81 @@ +import classNames from "classnames"; +import { Paper, Table, Title } from "@yoast/ui-library"; +import { SCORE_META } from "../scores/score-meta"; + +/** + * The score bullet component. + * + * @param {string} score The score. + * @returns {JSX.Element} The element. + */ +const ScoreBullet = ( { score } ) => ( +
+
+ + { SCORE_META[ score ].label } + +
+
+); + +/** + * The table head component. + * + * @param {JSX.Element} children The table headers. + * @returns {JSX.Element} The element. + */ +const TableHead = ( { children } ) => { + return + + { "" } + { children } + + ; +}; + +/** + * The table row component for the table widget includes numbering of first cell. + * + * @param {children} children The row cells. + * @param {number} index The row index. + * @param {string} key The row key. + * + * @returns {JSX.Element} The row element. + */ +const TableRow = ( { children, index, key } ) => { + return + { index + 1 }. + { children } + ; +}; + +/** + * The Site Kit table component. + * + * @param {string} title The table title. + * @param {JSX.Element} children The table rows. + * + * @returns {JSX.Element} The element. + */ +export const TableWidget = ( { title, children } ) => { + return ( + + + { title } + +
+ + { children } +
+
+
+ ); +}; + +TableWidget.Head = TableHead; +TableWidget.Row = TableRow; +TableWidget.ScoreBullet = ScoreBullet; +TableWidget.Cell = Table.Cell; +TableWidget.Header = Table.Header; +TableWidget.Body = Table.Body; + diff --git a/packages/js/src/dashboard/index.js b/packages/js/src/dashboard/index.js index 675c5bf5a83..3a6aca76c67 100644 --- a/packages/js/src/dashboard/index.js +++ b/packages/js/src/dashboard/index.js @@ -56,15 +56,11 @@ export { Dashboard } from "./components/dashboard"; */ /** - * @typedef {Object} Column A column in the table. - * @property {string} name The unique identifier. - * @property {string} label The user-facing label. - * @property {boolean} sortable Whether the column is sortable. - * @property {string} [className] The CSS class name for the column. + * @typedef {Object} MostPopularContent The most popular content data. + * @property {string} subject The landing page. + * @property {number} clicks The number of clicks. + * @property {number} impressions The number of impressions. + * @property {number} ctr The click-through rate. + * @property {number} position The average position. + * @property {number} seoScore The seo score. */ - -/** - * @typedef {Array>} Data The data for the table rows. - */ - - diff --git a/packages/js/tests/dashboard/components/dashboard-table.test.js b/packages/js/tests/dashboard/components/dashboard-table.test.js deleted file mode 100644 index a39abad6d2e..00000000000 --- a/packages/js/tests/dashboard/components/dashboard-table.test.js +++ /dev/null @@ -1,44 +0,0 @@ -import { render, screen } from "../../test-utils.js"; -import "@testing-library/jest-dom"; -import { DashboardTable } from "../../../src/dashboard/components/dashboard-table.js"; -import { SCORE_META } from "../../../src/dashboard/scores/score-meta.js"; - -describe( "DashboardTable", () => { - const columns = [ - { name: "landing-page", label: "Landing page" }, - { name: "clicks", label: "Clicks" }, - { name: "impressions", label: "Impressions" }, - { name: "ctr", label: "CTR" }, - { name: "average-position", label: "Average position" }, - { name: "seo-score", label: "SEO score" }, - ]; - - const data = [ - [ "https://example.com1", 10, 1001, 10, 1, "good" ], - [ "https://example.com2", 90, 1002, 9, 3, "bad" ], - [ "https://example.com3", 50, 1003, 8, 5, "good" ], - [ "https://example.com4", 0, 1004, 7, 2, "ok" ], - [ "https://example.com5", 70, 1005, 6, 4, "ok" ], - ]; - - it( "renders the table with correct title and columns", () => { - render( ); - expect( screen.getByText( "Test Table" ) ).toBeInTheDocument(); - columns.forEach( ( column ) => { - expect( screen.getByText( column.label ) ).toBeInTheDocument(); - } ); - } ); - - it( "renders the table rows correctly", () => { - render( ); - data.forEach( ( row ) => { - const rowElement = screen.getByText( row[ 0 ] ).closest( "tr" ); - expect( rowElement ).toHaveTextContent( row[ 0 ] ); - expect( rowElement ).toHaveTextContent( String( row[ 1 ] ) ); - expect( rowElement ).toHaveTextContent( String( row[ 2 ] ) ); - expect( rowElement ).toHaveTextContent( String( row[ 3 ] ) ); - expect( rowElement ).toHaveTextContent( String( row[ 4 ] ) ); - expect( rowElement ).toHaveTextContent( SCORE_META[ row[ 5 ] ].label ); - } ); - } ); -} ); diff --git a/packages/ui-library/src/elements/table/style.css b/packages/ui-library/src/elements/table/style.css index 9a6a86b0a5b..7c43cb7fb78 100644 --- a/packages/ui-library/src/elements/table/style.css +++ b/packages/ui-library/src/elements/table/style.css @@ -41,6 +41,10 @@ .yst-table--minimal { table { @apply yst-divide-y yst-divide-slate-300; + + th { + @apply yst-align-bottom; + } tbody { @apply yst-divide-y yst-divide-gray-200 yst-bg-white; } From febd022b43cf4217a35be4c52f73c019d4e00315 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 22 Jan 2025 13:35:34 +0200 Subject: [PATCH 38/55] align seo score header --- packages/js/src/dashboard/components/most-popular-table.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/js/src/dashboard/components/most-popular-table.js b/packages/js/src/dashboard/components/most-popular-table.js index 2222009e391..704679d38a1 100644 --- a/packages/js/src/dashboard/components/most-popular-table.js +++ b/packages/js/src/dashboard/components/most-popular-table.js @@ -20,10 +20,8 @@ export const MostPopularTable = ( { data } ) => { { __( "Impressions", "wordpress-seo" ) } { __( "CTR", "wordpress-seo" ) } { __( "Average position", "wordpress-seo" ) } - -
- { __( "SEO score", "wordpress-seo" ) } -
+ + { __( "SEO score", "wordpress-seo" ) } From 95634c36142ba756a783de9a306c28573fdfd911 Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Wed, 22 Jan 2025 13:35:32 +0100 Subject: [PATCH 39/55] Lowercasing the moduleResolution option --- packages/yoastseo/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoastseo/tsconfig.json b/packages/yoastseo/tsconfig.json index 015b1326585..76266a1b2c2 100644 --- a/packages/yoastseo/tsconfig.json +++ b/packages/yoastseo/tsconfig.json @@ -10,7 +10,7 @@ "checkJs": false, "outDir": "./types", "module": "esnext", - "moduleResolution": "Bundler", + "moduleResolution": "bundler", "strict": true, "skipLibCheck": true, } From b030bf4badcaf3b12579c4aa0a8ca587f9dafaa2 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Thu, 23 Jan 2025 09:21:14 +0100 Subject: [PATCH 40/55] Add layer in the application to split out the route from the application. --- .../search-rankings/top-page-collector.php | 42 +++++++++++++++++++ .../search-rankings/top-query-collector.php} | 21 +++------- ...-interface.php => collector-interface.php} | 2 +- .../site-kit-search-console-adapter.php | 16 +++++-- .../search-rankings-parser.php | 30 ------------- .../abstract-ranking-route.php | 14 +++---- .../search-rankings/top-page-route.php | 8 ++-- .../search-rankings/top-query-route.php | 8 ++-- 8 files changed, 75 insertions(+), 66 deletions(-) create mode 100644 src/dashboard/application/search-rankings/top-page-collector.php rename src/dashboard/{domain/search-rankings/search-rankings-data-provider.php => application/search-rankings/top-query-collector.php} (61%) rename src/dashboard/domain/data-provider/{data-provider-interface.php => collector-interface.php} (93%) delete mode 100644 src/dashboard/infrastructure/search-rankings/search-rankings-parser.php diff --git a/src/dashboard/application/search-rankings/top-page-collector.php b/src/dashboard/application/search-rankings/top-page-collector.php new file mode 100644 index 00000000000..a4a4ad69826 --- /dev/null +++ b/src/dashboard/application/search-rankings/top-page-collector.php @@ -0,0 +1,42 @@ +site_kit_search_console_adapter = $site_kit_search_console_adapter; + } + + /** + * Method to get search related data from a provider. + * + * @param Parameters $parameters The parameter to get the search data for. + * + * @return Data_Container + */ + public function get_data( Parameters $parameters ): Data_Container { + + return $this->site_kit_search_console_adapter->get_data( $parameters ); + } +} diff --git a/src/dashboard/domain/search-rankings/search-rankings-data-provider.php b/src/dashboard/application/search-rankings/top-query-collector.php similarity index 61% rename from src/dashboard/domain/search-rankings/search-rankings-data-provider.php rename to src/dashboard/application/search-rankings/top-query-collector.php index 75f4eec26a6..40e0e9d443b 100644 --- a/src/dashboard/domain/search-rankings/search-rankings-data-provider.php +++ b/src/dashboard/application/search-rankings/top-query-collector.php @@ -1,9 +1,9 @@ site_kit_search_console_adapter = $site_kit_search_console_adapter; - $this->search_rankings_parser = $search_rankings_parser; } /** @@ -47,8 +38,6 @@ public function __construct( Site_Kit_Search_Console_Adapter $site_kit_search_co */ public function get_data( Parameters $parameters ): Data_Container { - $results = $this->site_kit_search_console_adapter->get_data( $parameters ); - - return $this->search_rankings_parser->parse( $results ); + return $this->site_kit_search_console_adapter->get_data( $parameters ); } } diff --git a/src/dashboard/domain/data-provider/data-provider-interface.php b/src/dashboard/domain/data-provider/collector-interface.php similarity index 93% rename from src/dashboard/domain/data-provider/data-provider-interface.php rename to src/dashboard/domain/data-provider/collector-interface.php index b7d5c444c3a..fa4f40c0efe 100644 --- a/src/dashboard/domain/data-provider/data-provider-interface.php +++ b/src/dashboard/domain/data-provider/collector-interface.php @@ -5,7 +5,7 @@ /** * Interface describing the way to get data for a specific data provider. */ -interface Data_Provider_Interface { +interface Collector_Interface { /** * Method to get search related data from a provider. diff --git a/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php b/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php index 823bb5b980b..3e49c9ee0af 100644 --- a/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php +++ b/src/dashboard/infrastructure/search-console/site-kit-search-console-adapter.php @@ -7,8 +7,9 @@ use Google\Site_Kit\Core\Modules\Modules; use Google\Site_Kit\Modules\Search_Console; use Google\Site_Kit\Plugin; -use Google\Site_Kit_Dependencies\Google\Service\SearchConsole\ApiDataRow; use WP_Error; +use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container; +use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Search_Data; /** * The site API adapter to make calls via the Site_Kit plugin. @@ -40,9 +41,9 @@ public function __construct() { * * @param Search_Console_Parameters $parameters The parameters. * - * @return ApiDataRow[]|WP_Error Data on success, or WP_Error on failure. + * @return Data_Container|WP_Error Data on success, or WP_Error on failure. */ - public function get_data( Search_Console_Parameters $parameters ) { + public function get_data( Search_Console_Parameters $parameters ): Data_Container { $api_parameters = [ 'slug' => 'search-console', 'datapoint' => 'searchanalytics', @@ -52,6 +53,13 @@ public function get_data( Search_Console_Parameters $parameters ) { 'dimensions' => $parameters->get_dimensions(), ]; - return self::$search_console_module->get_data( 'searchanalytics', $api_parameters ); + $data_rows = self::$search_console_module->get_data( 'searchanalytics', $api_parameters ); + + $search_data_container = new Data_Container(); + foreach ( $data_rows as $ranking ) { + $search_data_container->add_data( new Search_Data( $ranking->clicks, $ranking->ctr, $ranking->impressions, $ranking->position, $ranking->keys[0] ) ); + } + + return $search_data_container; } } diff --git a/src/dashboard/infrastructure/search-rankings/search-rankings-parser.php b/src/dashboard/infrastructure/search-rankings/search-rankings-parser.php deleted file mode 100644 index dc4e1246ead..00000000000 --- a/src/dashboard/infrastructure/search-rankings/search-rankings-parser.php +++ /dev/null @@ -1,30 +0,0 @@ -add_data( new Search_Data( $ranking->clicks, $ranking->ctr, $ranking->impressions, $ranking->position, $ranking->keys[0] ) ); - } - - return $search_data_container; - } -} diff --git a/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php index fe8569202b3..edfb941c8b7 100644 --- a/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php +++ b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php @@ -9,7 +9,7 @@ use WP_REST_Response; use WPSEO_Capability_Utils; use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional; -use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Search_Rankings_Data_Provider; +use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Collector_Interface; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Search_Console_Parameters; use Yoast\WP\SEO\Main; use Yoast\WP\SEO\Routes\Route_Interface; @@ -42,9 +42,9 @@ abstract class Abstract_Ranking_Route implements Route_Interface { /** * The data provider. * - * @var Search_Rankings_Data_Provider $search_rankings_data_provider + * @var Collector_Interface $search_rankings_collector */ - private $search_rankings_data_provider; + private $search_rankings_collector; /** * Returns the needed conditionals. @@ -58,10 +58,10 @@ public static function get_conditionals(): array { /** * The constructor. * - * @param Search_Rankings_Data_Provider $search_rankings_data_provider The data provider. + * @param Collector_Interface $search_rankings_collector The data provider. */ - public function __construct( Search_Rankings_Data_Provider $search_rankings_data_provider ) { - $this->search_rankings_data_provider = $search_rankings_data_provider; + public function __construct( Collector_Interface $search_rankings_collector ) { + $this->search_rankings_collector = $search_rankings_collector; } /** @@ -138,7 +138,7 @@ public function get_rankings( WP_REST_Request $request ): WP_REST_Response { $this->request_parameters->set_start_date( $date->format( 'Y-m-d' ) ); $this->request_parameters->set_end_date( ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->format( 'Y-m-d' ) ); - $search_data_container = $this->search_rankings_data_provider->get_data( $this->request_parameters ); + $search_data_container = $this->search_rankings_collector->get_data( $this->request_parameters ); } catch ( Exception $exception ) { return new WP_REST_Response( diff --git a/src/dashboard/user-interface/search-rankings/top-page-route.php b/src/dashboard/user-interface/search-rankings/top-page-route.php index 62d71589656..f96a8f69655 100644 --- a/src/dashboard/user-interface/search-rankings/top-page-route.php +++ b/src/dashboard/user-interface/search-rankings/top-page-route.php @@ -2,7 +2,7 @@ // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Search_Rankings; -use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Search_Rankings_Data_Provider; +use Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Top_Page_Collector; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Search_Console_Parameters; /** @@ -20,11 +20,11 @@ class Top_Page_Route extends Abstract_Ranking_Route { /** * The constructor. * - * @param Search_Rankings_Data_Provider $search_rankings_data_provider The data provider. + * @param Top_Page_Collector $top_page_collector The data provider. */ - public function __construct( Search_Rankings_Data_Provider $search_rankings_data_provider ) { + public function __construct( Top_Page_Collector $top_page_collector ) { $this->set_request_parameters( new Search_Console_Parameters( [ 'page' ] ) ); - parent::__construct( $search_rankings_data_provider ); + parent::__construct( $top_page_collector ); } } diff --git a/src/dashboard/user-interface/search-rankings/top-query-route.php b/src/dashboard/user-interface/search-rankings/top-query-route.php index 91ba75a8d72..ad57089ad27 100644 --- a/src/dashboard/user-interface/search-rankings/top-query-route.php +++ b/src/dashboard/user-interface/search-rankings/top-query-route.php @@ -2,7 +2,7 @@ // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Search_Rankings; -use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Search_Rankings_Data_Provider; +use Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Top_Page_Collector; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Search_Console_Parameters; /** @@ -20,11 +20,11 @@ class Top_Query_Route extends Abstract_Ranking_Route { /** * The constructor. * - * @param Search_Rankings_Data_Provider $search_rankings_data_provider The data provider. + * @param Top_Page_Collector $top_page_collector The data provider. */ - public function __construct( Search_Rankings_Data_Provider $search_rankings_data_provider ) { + public function __construct( Top_Page_Collector $top_page_collector ) { $this->set_request_parameters( new Search_Console_Parameters( [ 'query' ] ) ); - parent::__construct( $search_rankings_data_provider ); + parent::__construct( $top_page_collector ); } } From ad27733f6591aad45b239387acc8eb15ac102333 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Thu, 23 Jan 2025 09:39:02 +0100 Subject: [PATCH 41/55] cs. --- src/dashboard/application/search-rankings/top-page-collector.php | 1 - .../application/search-rankings/top-query-collector.php | 1 - 2 files changed, 2 deletions(-) diff --git a/src/dashboard/application/search-rankings/top-page-collector.php b/src/dashboard/application/search-rankings/top-page-collector.php index a4a4ad69826..cf02e0a638f 100644 --- a/src/dashboard/application/search-rankings/top-page-collector.php +++ b/src/dashboard/application/search-rankings/top-page-collector.php @@ -6,7 +6,6 @@ use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Site_Kit_Search_Console_Adapter; -use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Rankings\Search_Rankings_Parser; /** * The data provider for search ranking related data. diff --git a/src/dashboard/application/search-rankings/top-query-collector.php b/src/dashboard/application/search-rankings/top-query-collector.php index 40e0e9d443b..a3df133caf4 100644 --- a/src/dashboard/application/search-rankings/top-query-collector.php +++ b/src/dashboard/application/search-rankings/top-query-collector.php @@ -6,7 +6,6 @@ use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Site_Kit_Search_Console_Adapter; -use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Rankings\Search_Rankings_Parser; /** * The data provider for search ranking related data. From 08c925661d17180a59f516f3180ebe6938dcef4c Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Thu, 23 Jan 2025 09:42:17 +0100 Subject: [PATCH 42/55] :sob: --- .../application/search-rankings/top-page-collector.php | 1 + .../application/search-rankings/top-query-collector.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dashboard/application/search-rankings/top-page-collector.php b/src/dashboard/application/search-rankings/top-page-collector.php index cf02e0a638f..1ef99e2fcf3 100644 --- a/src/dashboard/application/search-rankings/top-page-collector.php +++ b/src/dashboard/application/search-rankings/top-page-collector.php @@ -18,6 +18,7 @@ class Top_Page_Collector implements Collector_Interface { * @var Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter */ private $site_kit_search_console_adapter; + /** * The constructor. * diff --git a/src/dashboard/application/search-rankings/top-query-collector.php b/src/dashboard/application/search-rankings/top-query-collector.php index a3df133caf4..99773a613f4 100644 --- a/src/dashboard/application/search-rankings/top-query-collector.php +++ b/src/dashboard/application/search-rankings/top-query-collector.php @@ -24,7 +24,7 @@ class Top_Query_Collector implements Collector_Interface { * * @param Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter The adapter. */ - public function __construct( Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter) { + public function __construct( Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter ) { $this->site_kit_search_console_adapter = $site_kit_search_console_adapter; } From 7097c2d8137fff0439682ae8df534d8400c0c922 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Thu, 23 Jan 2025 11:22:00 +0100 Subject: [PATCH 43/55] Rename to repository --- .../{top-page-collector.php => top-page-repository.php} | 4 ++-- .../{top-query-collector.php => top-query-repository.php} | 4 ++-- ...r-interface.php => dashboard-repository-interface.php} | 2 +- .../search-rankings/abstract-ranking-route.php | 8 ++++---- .../user-interface/search-rankings/top-page-route.php | 6 +++--- .../user-interface/search-rankings/top-query-route.php | 6 +++--- 6 files changed, 15 insertions(+), 15 deletions(-) rename src/dashboard/application/search-rankings/{top-page-collector.php => top-page-repository.php} (89%) rename src/dashboard/application/search-rankings/{top-query-collector.php => top-query-repository.php} (89%) rename src/dashboard/domain/data-provider/{collector-interface.php => dashboard-repository-interface.php} (91%) diff --git a/src/dashboard/application/search-rankings/top-page-collector.php b/src/dashboard/application/search-rankings/top-page-repository.php similarity index 89% rename from src/dashboard/application/search-rankings/top-page-collector.php rename to src/dashboard/application/search-rankings/top-page-repository.php index 1ef99e2fcf3..7083bb66042 100644 --- a/src/dashboard/application/search-rankings/top-page-collector.php +++ b/src/dashboard/application/search-rankings/top-page-repository.php @@ -2,7 +2,7 @@ // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Application\Search_Rankings; -use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Collector_Interface; +use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Dashboard_Repository_Interface; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Site_Kit_Search_Console_Adapter; @@ -10,7 +10,7 @@ /** * The data provider for search ranking related data. */ -class Top_Page_Collector implements Collector_Interface { +class Top_Page_Repository implements Dashboard_Repository_Interface { /** * The adapter. diff --git a/src/dashboard/application/search-rankings/top-query-collector.php b/src/dashboard/application/search-rankings/top-query-repository.php similarity index 89% rename from src/dashboard/application/search-rankings/top-query-collector.php rename to src/dashboard/application/search-rankings/top-query-repository.php index 99773a613f4..ee9c05a63d1 100644 --- a/src/dashboard/application/search-rankings/top-query-collector.php +++ b/src/dashboard/application/search-rankings/top-query-repository.php @@ -2,7 +2,7 @@ // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Application\Search_Rankings; -use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Collector_Interface; +use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Dashboard_Repository_Interface; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Site_Kit_Search_Console_Adapter; @@ -10,7 +10,7 @@ /** * The data provider for search ranking related data. */ -class Top_Query_Collector implements Collector_Interface { +class Top_Query_Repository implements Dashboard_Repository_Interface { /** * The adapter. diff --git a/src/dashboard/domain/data-provider/collector-interface.php b/src/dashboard/domain/data-provider/dashboard-repository-interface.php similarity index 91% rename from src/dashboard/domain/data-provider/collector-interface.php rename to src/dashboard/domain/data-provider/dashboard-repository-interface.php index fa4f40c0efe..9a24f78923d 100644 --- a/src/dashboard/domain/data-provider/collector-interface.php +++ b/src/dashboard/domain/data-provider/dashboard-repository-interface.php @@ -5,7 +5,7 @@ /** * Interface describing the way to get data for a specific data provider. */ -interface Collector_Interface { +interface Dashboard_Repository_Interface { /** * Method to get search related data from a provider. diff --git a/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php index edfb941c8b7..0647bd500fc 100644 --- a/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php +++ b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php @@ -9,7 +9,7 @@ use WP_REST_Response; use WPSEO_Capability_Utils; use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional; -use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Collector_Interface; +use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Dashboard_Repository_Interface; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Search_Console_Parameters; use Yoast\WP\SEO\Main; use Yoast\WP\SEO\Routes\Route_Interface; @@ -42,7 +42,7 @@ abstract class Abstract_Ranking_Route implements Route_Interface { /** * The data provider. * - * @var Collector_Interface $search_rankings_collector + * @var Dashboard_Repository_Interface $search_rankings_collector */ private $search_rankings_collector; @@ -58,9 +58,9 @@ public static function get_conditionals(): array { /** * The constructor. * - * @param Collector_Interface $search_rankings_collector The data provider. + * @param Dashboard_Repository_Interface $search_rankings_collector The data provider. */ - public function __construct( Collector_Interface $search_rankings_collector ) { + public function __construct( Dashboard_Repository_Interface $search_rankings_collector ) { $this->search_rankings_collector = $search_rankings_collector; } diff --git a/src/dashboard/user-interface/search-rankings/top-page-route.php b/src/dashboard/user-interface/search-rankings/top-page-route.php index f96a8f69655..fd3c88c8c86 100644 --- a/src/dashboard/user-interface/search-rankings/top-page-route.php +++ b/src/dashboard/user-interface/search-rankings/top-page-route.php @@ -2,7 +2,7 @@ // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Search_Rankings; -use Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Top_Page_Collector; +use Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Top_Page_Repository; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Search_Console_Parameters; /** @@ -20,9 +20,9 @@ class Top_Page_Route extends Abstract_Ranking_Route { /** * The constructor. * - * @param Top_Page_Collector $top_page_collector The data provider. + * @param Top_Page_Repository $top_page_collector The data provider. */ - public function __construct( Top_Page_Collector $top_page_collector ) { + public function __construct( Top_Page_Repository $top_page_collector ) { $this->set_request_parameters( new Search_Console_Parameters( [ 'page' ] ) ); parent::__construct( $top_page_collector ); diff --git a/src/dashboard/user-interface/search-rankings/top-query-route.php b/src/dashboard/user-interface/search-rankings/top-query-route.php index ad57089ad27..fa50ac377e7 100644 --- a/src/dashboard/user-interface/search-rankings/top-query-route.php +++ b/src/dashboard/user-interface/search-rankings/top-query-route.php @@ -2,7 +2,7 @@ // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Search_Rankings; -use Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Top_Page_Collector; +use Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Top_Page_Repository; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Search_Console_Parameters; /** @@ -20,9 +20,9 @@ class Top_Query_Route extends Abstract_Ranking_Route { /** * The constructor. * - * @param Top_Page_Collector $top_page_collector The data provider. + * @param Top_Page_Repository $top_page_collector The data provider. */ - public function __construct( Top_Page_Collector $top_page_collector ) { + public function __construct( Top_Page_Repository $top_page_collector ) { $this->set_request_parameters( new Search_Console_Parameters( [ 'query' ] ) ); parent::__construct( $top_page_collector ); From c49cf49adfdcc47c39201d416e210d8476ae7f44 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 23 Jan 2025 12:39:02 +0200 Subject: [PATCH 44/55] add formatters --- packages/js/package.json | 2 +- .../js/src/dashboard/components/dashboard.js | 44 +++++++++++++++++++ .../components/most-popular-table.js | 32 +++++++++----- .../src/dashboard/components/table-widget.js | 30 +++---------- .../dashboard/formatters/SeoScoreFormatter.js | 18 ++++++++ .../src/dashboard/formatters/ctrFormatter.js | 10 +++++ packages/js/src/dashboard/formatters/index.js | 3 ++ .../dashboard/formatters/positionFormatter.js | 10 +++++ .../general-page-integration.php | 1 + 9 files changed, 113 insertions(+), 37 deletions(-) create mode 100644 packages/js/src/dashboard/formatters/SeoScoreFormatter.js create mode 100644 packages/js/src/dashboard/formatters/ctrFormatter.js create mode 100644 packages/js/src/dashboard/formatters/index.js create mode 100644 packages/js/src/dashboard/formatters/positionFormatter.js diff --git a/packages/js/package.json b/packages/js/package.json index 5f6d121ce15..8a3e08f4b7b 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "cd ../.. && wp-scripts build --config config/webpack/webpack.config.js", "test": "jest", - "lint": "eslint . --max-warnings=64" + "lint": "eslint . --max-warnings=63" }, "dependencies": { "@draft-js-plugins/mention": "^5.0.0", diff --git a/packages/js/src/dashboard/components/dashboard.js b/packages/js/src/dashboard/components/dashboard.js index 62f58e02edc..47d486a7488 100644 --- a/packages/js/src/dashboard/components/dashboard.js +++ b/packages/js/src/dashboard/components/dashboard.js @@ -1,5 +1,6 @@ import { Scores } from "../scores/components/scores"; import { PageTitle } from "./page-title"; +import { MostPopularTable } from "./most-popular-table"; /** * @type {import("../index").ContentType} ContentType @@ -18,9 +19,52 @@ import { PageTitle } from "./page-title"; * @returns {JSX.Element} The element. */ export const Dashboard = ( { contentTypes, userName, features, endpoints, headers, links } ) => { + const data = [ + { + subject: "https://example.com/page1", + clicks: 100, + impressions: 1000, + ctr: 0.020383459755, + averagePosition: 5.568768, + seoScore: "good", + }, + { + subject: "https://example.com/page2", + clicks: 200, + impressions: 2000, + ctr: 0.62448974546, + averagePosition: 10.5479879879, + seoScore: "ok", + }, + { + subject: "https://example.com/page3", + clicks: 300, + impressions: 3000, + ctr: 0.05897354354, + averagePosition: 15.3216544, + seoScore: "bad", + }, + { + subject: "https://example.com/page4", + clicks: 400, + impressions: 4000, + ctr: 0.98456465, + averagePosition: 20.6756456, + seoScore: "good", + }, + { + subject: "https://example.com/page5", + clicks: 500, + impressions: 5000, + ctr: 0.1256465465, + averagePosition: 25.467686, + seoScore: "ok", + }, + ]; return ( <> +
{ features.indexables && features.seoAnalysis && ( diff --git a/packages/js/src/dashboard/components/most-popular-table.js b/packages/js/src/dashboard/components/most-popular-table.js index 704679d38a1..0cd19036532 100644 --- a/packages/js/src/dashboard/components/most-popular-table.js +++ b/packages/js/src/dashboard/components/most-popular-table.js @@ -1,5 +1,8 @@ import { __ } from "@wordpress/i18n"; +import { select } from "@wordpress/data"; +import { STORE_NAME } from "../../general/constants"; import { TableWidget } from "./table-widget"; +import { ctrFormatter, positionFormatter, SeoScoreFormatter } from "../formatters"; /** * @type {import("../index").MostPopularContent} Most popular content @@ -13,26 +16,33 @@ import { TableWidget } from "./table-widget"; * @returns {JSX.Element} The element. */ export const MostPopularTable = ( { data } ) => { + const languageCode = select( STORE_NAME ).selectPreference( "languageCode" ); return { __( "Landing page", "wordpress-seo" ) } - { __( "Clicks", "wordpress-seo" ) } - { __( "Impressions", "wordpress-seo" ) } - { __( "CTR", "wordpress-seo" ) } - { __( "Average position", "wordpress-seo" ) } + { __( "Clicks", "wordpress-seo" ) } + { __( "Impressions", "wordpress-seo" ) } + { __( "CTR", "wordpress-seo" ) } + + { __( "Average position", "wordpress-seo" ) } + - { __( "SEO score", "wordpress-seo" ) } +
+
+ { __( "SEO score", "wordpress-seo" ) } +
+
{ data.map( ( { subject, clicks, impressions, ctr, averagePosition, seoScore }, index ) => ( - { subject } - { clicks } - { impressions } - { ctr } - { averagePosition } - + { subject } + { clicks } + { impressions } + { ctrFormatter( ctr, languageCode ) } + { positionFormatter( averagePosition, languageCode ) } + ) ) } diff --git a/packages/js/src/dashboard/components/table-widget.js b/packages/js/src/dashboard/components/table-widget.js index ea11ac99653..2bfe217353d 100644 --- a/packages/js/src/dashboard/components/table-widget.js +++ b/packages/js/src/dashboard/components/table-widget.js @@ -1,22 +1,4 @@ -import classNames from "classnames"; import { Paper, Table, Title } from "@yoast/ui-library"; -import { SCORE_META } from "../scores/score-meta"; - -/** - * The score bullet component. - * - * @param {string} score The score. - * @returns {JSX.Element} The element. - */ -const ScoreBullet = ( { score } ) => ( -
-
- - { SCORE_META[ score ].label } - -
-
-); /** * The table head component. @@ -27,7 +9,7 @@ const ScoreBullet = ( { score } ) => ( const TableHead = ( { children } ) => { return - { "" } + { "" } { children } ; @@ -38,13 +20,12 @@ const TableHead = ( { children } ) => { * * @param {children} children The row cells. * @param {number} index The row index. - * @param {string} key The row key. * * @returns {JSX.Element} The row element. */ -const TableRow = ( { children, index, key } ) => { - return - { index + 1 }. +const TableRow = ( { children, index } ) => { + return + { index + 1 }. { children } ; }; @@ -59,7 +40,7 @@ const TableRow = ( { children, index, key } ) => { */ export const TableWidget = ( { title, children } ) => { return ( - + { title } @@ -74,7 +55,6 @@ export const TableWidget = ( { title, children } ) => { TableWidget.Head = TableHead; TableWidget.Row = TableRow; -TableWidget.ScoreBullet = ScoreBullet; TableWidget.Cell = Table.Cell; TableWidget.Header = Table.Header; TableWidget.Body = Table.Body; diff --git a/packages/js/src/dashboard/formatters/SeoScoreFormatter.js b/packages/js/src/dashboard/formatters/SeoScoreFormatter.js new file mode 100644 index 00000000000..bd59dddbeb4 --- /dev/null +++ b/packages/js/src/dashboard/formatters/SeoScoreFormatter.js @@ -0,0 +1,18 @@ +import classNames from "classnames"; +import { SCORE_META } from "../scores/score-meta"; + +/** + * The score bullet component. + * + * @param {string} score The score. + * @returns {JSX.Element} The element. + */ +export const SeoScoreFormatter = ( { score } ) => ( +
+
+ + { SCORE_META[ score ].label } + +
+
+); diff --git a/packages/js/src/dashboard/formatters/ctrFormatter.js b/packages/js/src/dashboard/formatters/ctrFormatter.js new file mode 100644 index 00000000000..a7e6a02cb32 --- /dev/null +++ b/packages/js/src/dashboard/formatters/ctrFormatter.js @@ -0,0 +1,10 @@ +/** + * Convert decimal position number. + * + * @param {number} ctr The number of ctr. + * + * @returns {string} The formatted ctr. + */ +export const ctrFormatter = ( ctr, languageCode ) => { + return new Intl.NumberFormat( languageCode, { style: "percent", minimumFractionDigits: 2, maximumFractionDigits: 2 } ).format( ctr ); +}; diff --git a/packages/js/src/dashboard/formatters/index.js b/packages/js/src/dashboard/formatters/index.js new file mode 100644 index 00000000000..aaf1fcd3721 --- /dev/null +++ b/packages/js/src/dashboard/formatters/index.js @@ -0,0 +1,3 @@ +export { ctrFormatter } from "./ctrFormatter"; +export { positionFormatter } from "./positionFormatter"; +export { SeoScoreFormatter } from "./SeoScoreFormatter"; diff --git a/packages/js/src/dashboard/formatters/positionFormatter.js b/packages/js/src/dashboard/formatters/positionFormatter.js new file mode 100644 index 00000000000..558752e5acd --- /dev/null +++ b/packages/js/src/dashboard/formatters/positionFormatter.js @@ -0,0 +1,10 @@ +/** + * Convert position number. + * + * @param {number} position The number of position. + * + * @returns {string} The formatted position. + */ +export const positionFormatter = ( position, languageCode ) => { + return new Intl.NumberFormat( languageCode, { minimumFractionDigits: 2, maximumFractionDigits: 2 } ).format( position ); +}; diff --git a/src/general/user-interface/general-page-integration.php b/src/general/user-interface/general-page-integration.php index 0a119835477..9c0b6b91cf9 100644 --- a/src/general/user-interface/general-page-integration.php +++ b/src/general/user-interface/general-page-integration.php @@ -207,6 +207,7 @@ private function get_script_data() { 'actionId' => 'load-nfd-ctb', 'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2', ], + 'languageCode' => explode('_', \get_locale())[0], ], 'linkParams' => $this->shortlink_helper->get_query_params(), 'userEditUrl' => \add_query_arg( 'user_id', '{user_id}', \admin_url( 'user-edit.php' ) ), From c46bf26912993cd66ecac18428d48418ba04eb08 Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Thu, 23 Jan 2025 12:52:40 +0200 Subject: [PATCH 45/55] Cascade the rename of collector into repository to all the places --- .../search-rankings/abstract-ranking-route.php | 12 ++++++------ .../search-rankings/top-page-route.php | 6 +++--- .../search-rankings/top-query-route.php | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php index 0647bd500fc..90b10b5aebe 100644 --- a/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php +++ b/src/dashboard/user-interface/search-rankings/abstract-ranking-route.php @@ -42,9 +42,9 @@ abstract class Abstract_Ranking_Route implements Route_Interface { /** * The data provider. * - * @var Dashboard_Repository_Interface $search_rankings_collector + * @var Dashboard_Repository_Interface $search_rankings_repository */ - private $search_rankings_collector; + private $search_rankings_repository; /** * Returns the needed conditionals. @@ -58,10 +58,10 @@ public static function get_conditionals(): array { /** * The constructor. * - * @param Dashboard_Repository_Interface $search_rankings_collector The data provider. + * @param Dashboard_Repository_Interface $search_rankings_repository The data provider. */ - public function __construct( Dashboard_Repository_Interface $search_rankings_collector ) { - $this->search_rankings_collector = $search_rankings_collector; + public function __construct( Dashboard_Repository_Interface $search_rankings_repository ) { + $this->search_rankings_repository = $search_rankings_repository; } /** @@ -138,7 +138,7 @@ public function get_rankings( WP_REST_Request $request ): WP_REST_Response { $this->request_parameters->set_start_date( $date->format( 'Y-m-d' ) ); $this->request_parameters->set_end_date( ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->format( 'Y-m-d' ) ); - $search_data_container = $this->search_rankings_collector->get_data( $this->request_parameters ); + $search_data_container = $this->search_rankings_repository->get_data( $this->request_parameters ); } catch ( Exception $exception ) { return new WP_REST_Response( diff --git a/src/dashboard/user-interface/search-rankings/top-page-route.php b/src/dashboard/user-interface/search-rankings/top-page-route.php index fd3c88c8c86..6c0a443fc1e 100644 --- a/src/dashboard/user-interface/search-rankings/top-page-route.php +++ b/src/dashboard/user-interface/search-rankings/top-page-route.php @@ -20,11 +20,11 @@ class Top_Page_Route extends Abstract_Ranking_Route { /** * The constructor. * - * @param Top_Page_Repository $top_page_collector The data provider. + * @param Top_Page_Repository $top_page_repository The data provider. */ - public function __construct( Top_Page_Repository $top_page_collector ) { + public function __construct( Top_Page_Repository $top_page_repository ) { $this->set_request_parameters( new Search_Console_Parameters( [ 'page' ] ) ); - parent::__construct( $top_page_collector ); + parent::__construct( $top_page_repository ); } } diff --git a/src/dashboard/user-interface/search-rankings/top-query-route.php b/src/dashboard/user-interface/search-rankings/top-query-route.php index fa50ac377e7..126dae619b0 100644 --- a/src/dashboard/user-interface/search-rankings/top-query-route.php +++ b/src/dashboard/user-interface/search-rankings/top-query-route.php @@ -2,7 +2,7 @@ // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Search_Rankings; -use Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Top_Page_Repository; +use Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Top_Query_Repository; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Search_Console_Parameters; /** @@ -20,11 +20,11 @@ class Top_Query_Route extends Abstract_Ranking_Route { /** * The constructor. * - * @param Top_Page_Repository $top_page_collector The data provider. + * @param Top_Query_Repository $top_query_repository The data provider. */ - public function __construct( Top_Page_Repository $top_page_collector ) { + public function __construct( Top_Query_Repository $top_query_repository ) { $this->set_request_parameters( new Search_Console_Parameters( [ 'query' ] ) ); - parent::__construct( $top_page_collector ); + parent::__construct( $top_query_repository ); } } From 4cddd2793712a297cea414a51a9cb8f68c5aa3fa Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 23 Jan 2025 15:40:31 +0200 Subject: [PATCH 46/55] Revert "add formatters" This reverts commit c49cf49adfdcc47c39201d416e210d8476ae7f44. --- packages/js/package.json | 2 +- .../js/src/dashboard/components/dashboard.js | 44 ------------------- .../components/most-popular-table.js | 32 +++++--------- .../src/dashboard/components/table-widget.js | 30 ++++++++++--- .../dashboard/formatters/SeoScoreFormatter.js | 18 -------- .../src/dashboard/formatters/ctrFormatter.js | 10 ----- packages/js/src/dashboard/formatters/index.js | 3 -- .../dashboard/formatters/positionFormatter.js | 10 ----- .../general-page-integration.php | 1 - 9 files changed, 37 insertions(+), 113 deletions(-) delete mode 100644 packages/js/src/dashboard/formatters/SeoScoreFormatter.js delete mode 100644 packages/js/src/dashboard/formatters/ctrFormatter.js delete mode 100644 packages/js/src/dashboard/formatters/index.js delete mode 100644 packages/js/src/dashboard/formatters/positionFormatter.js diff --git a/packages/js/package.json b/packages/js/package.json index 8a3e08f4b7b..5f6d121ce15 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "cd ../.. && wp-scripts build --config config/webpack/webpack.config.js", "test": "jest", - "lint": "eslint . --max-warnings=63" + "lint": "eslint . --max-warnings=64" }, "dependencies": { "@draft-js-plugins/mention": "^5.0.0", diff --git a/packages/js/src/dashboard/components/dashboard.js b/packages/js/src/dashboard/components/dashboard.js index 47d486a7488..62f58e02edc 100644 --- a/packages/js/src/dashboard/components/dashboard.js +++ b/packages/js/src/dashboard/components/dashboard.js @@ -1,6 +1,5 @@ import { Scores } from "../scores/components/scores"; import { PageTitle } from "./page-title"; -import { MostPopularTable } from "./most-popular-table"; /** * @type {import("../index").ContentType} ContentType @@ -19,52 +18,9 @@ import { MostPopularTable } from "./most-popular-table"; * @returns {JSX.Element} The element. */ export const Dashboard = ( { contentTypes, userName, features, endpoints, headers, links } ) => { - const data = [ - { - subject: "https://example.com/page1", - clicks: 100, - impressions: 1000, - ctr: 0.020383459755, - averagePosition: 5.568768, - seoScore: "good", - }, - { - subject: "https://example.com/page2", - clicks: 200, - impressions: 2000, - ctr: 0.62448974546, - averagePosition: 10.5479879879, - seoScore: "ok", - }, - { - subject: "https://example.com/page3", - clicks: 300, - impressions: 3000, - ctr: 0.05897354354, - averagePosition: 15.3216544, - seoScore: "bad", - }, - { - subject: "https://example.com/page4", - clicks: 400, - impressions: 4000, - ctr: 0.98456465, - averagePosition: 20.6756456, - seoScore: "good", - }, - { - subject: "https://example.com/page5", - clicks: 500, - impressions: 5000, - ctr: 0.1256465465, - averagePosition: 25.467686, - seoScore: "ok", - }, - ]; return ( <> -
{ features.indexables && features.seoAnalysis && ( diff --git a/packages/js/src/dashboard/components/most-popular-table.js b/packages/js/src/dashboard/components/most-popular-table.js index 0cd19036532..704679d38a1 100644 --- a/packages/js/src/dashboard/components/most-popular-table.js +++ b/packages/js/src/dashboard/components/most-popular-table.js @@ -1,8 +1,5 @@ import { __ } from "@wordpress/i18n"; -import { select } from "@wordpress/data"; -import { STORE_NAME } from "../../general/constants"; import { TableWidget } from "./table-widget"; -import { ctrFormatter, positionFormatter, SeoScoreFormatter } from "../formatters"; /** * @type {import("../index").MostPopularContent} Most popular content @@ -16,33 +13,26 @@ import { ctrFormatter, positionFormatter, SeoScoreFormatter } from "../formatter * @returns {JSX.Element} The element. */ export const MostPopularTable = ( { data } ) => { - const languageCode = select( STORE_NAME ).selectPreference( "languageCode" ); return { __( "Landing page", "wordpress-seo" ) } - { __( "Clicks", "wordpress-seo" ) } - { __( "Impressions", "wordpress-seo" ) } - { __( "CTR", "wordpress-seo" ) } - - { __( "Average position", "wordpress-seo" ) } - + { __( "Clicks", "wordpress-seo" ) } + { __( "Impressions", "wordpress-seo" ) } + { __( "CTR", "wordpress-seo" ) } + { __( "Average position", "wordpress-seo" ) } -
-
- { __( "SEO score", "wordpress-seo" ) } -
-
+ { __( "SEO score", "wordpress-seo" ) }
{ data.map( ( { subject, clicks, impressions, ctr, averagePosition, seoScore }, index ) => ( - { subject } - { clicks } - { impressions } - { ctrFormatter( ctr, languageCode ) } - { positionFormatter( averagePosition, languageCode ) } - + { subject } + { clicks } + { impressions } + { ctr } + { averagePosition } + ) ) } diff --git a/packages/js/src/dashboard/components/table-widget.js b/packages/js/src/dashboard/components/table-widget.js index 2bfe217353d..ea11ac99653 100644 --- a/packages/js/src/dashboard/components/table-widget.js +++ b/packages/js/src/dashboard/components/table-widget.js @@ -1,4 +1,22 @@ +import classNames from "classnames"; import { Paper, Table, Title } from "@yoast/ui-library"; +import { SCORE_META } from "../scores/score-meta"; + +/** + * The score bullet component. + * + * @param {string} score The score. + * @returns {JSX.Element} The element. + */ +const ScoreBullet = ( { score } ) => ( +
+
+ + { SCORE_META[ score ].label } + +
+
+); /** * The table head component. @@ -9,7 +27,7 @@ import { Paper, Table, Title } from "@yoast/ui-library"; const TableHead = ( { children } ) => { return - { "" } + { "" } { children } ; @@ -20,12 +38,13 @@ const TableHead = ( { children } ) => { * * @param {children} children The row cells. * @param {number} index The row index. + * @param {string} key The row key. * * @returns {JSX.Element} The row element. */ -const TableRow = ( { children, index } ) => { - return - { index + 1 }. +const TableRow = ( { children, index, key } ) => { + return + { index + 1 }. { children } ; }; @@ -40,7 +59,7 @@ const TableRow = ( { children, index } ) => { */ export const TableWidget = ( { title, children } ) => { return ( - + { title } @@ -55,6 +74,7 @@ export const TableWidget = ( { title, children } ) => { TableWidget.Head = TableHead; TableWidget.Row = TableRow; +TableWidget.ScoreBullet = ScoreBullet; TableWidget.Cell = Table.Cell; TableWidget.Header = Table.Header; TableWidget.Body = Table.Body; diff --git a/packages/js/src/dashboard/formatters/SeoScoreFormatter.js b/packages/js/src/dashboard/formatters/SeoScoreFormatter.js deleted file mode 100644 index bd59dddbeb4..00000000000 --- a/packages/js/src/dashboard/formatters/SeoScoreFormatter.js +++ /dev/null @@ -1,18 +0,0 @@ -import classNames from "classnames"; -import { SCORE_META } from "../scores/score-meta"; - -/** - * The score bullet component. - * - * @param {string} score The score. - * @returns {JSX.Element} The element. - */ -export const SeoScoreFormatter = ( { score } ) => ( -
-
- - { SCORE_META[ score ].label } - -
-
-); diff --git a/packages/js/src/dashboard/formatters/ctrFormatter.js b/packages/js/src/dashboard/formatters/ctrFormatter.js deleted file mode 100644 index a7e6a02cb32..00000000000 --- a/packages/js/src/dashboard/formatters/ctrFormatter.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Convert decimal position number. - * - * @param {number} ctr The number of ctr. - * - * @returns {string} The formatted ctr. - */ -export const ctrFormatter = ( ctr, languageCode ) => { - return new Intl.NumberFormat( languageCode, { style: "percent", minimumFractionDigits: 2, maximumFractionDigits: 2 } ).format( ctr ); -}; diff --git a/packages/js/src/dashboard/formatters/index.js b/packages/js/src/dashboard/formatters/index.js deleted file mode 100644 index aaf1fcd3721..00000000000 --- a/packages/js/src/dashboard/formatters/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { ctrFormatter } from "./ctrFormatter"; -export { positionFormatter } from "./positionFormatter"; -export { SeoScoreFormatter } from "./SeoScoreFormatter"; diff --git a/packages/js/src/dashboard/formatters/positionFormatter.js b/packages/js/src/dashboard/formatters/positionFormatter.js deleted file mode 100644 index 558752e5acd..00000000000 --- a/packages/js/src/dashboard/formatters/positionFormatter.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Convert position number. - * - * @param {number} position The number of position. - * - * @returns {string} The formatted position. - */ -export const positionFormatter = ( position, languageCode ) => { - return new Intl.NumberFormat( languageCode, { minimumFractionDigits: 2, maximumFractionDigits: 2 } ).format( position ); -}; diff --git a/src/general/user-interface/general-page-integration.php b/src/general/user-interface/general-page-integration.php index 9c0b6b91cf9..0a119835477 100644 --- a/src/general/user-interface/general-page-integration.php +++ b/src/general/user-interface/general-page-integration.php @@ -207,7 +207,6 @@ private function get_script_data() { 'actionId' => 'load-nfd-ctb', 'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2', ], - 'languageCode' => explode('_', \get_locale())[0], ], 'linkParams' => $this->shortlink_helper->get_query_params(), 'userEditUrl' => \add_query_arg( 'user_id', '{user_id}', \admin_url( 'user-edit.php' ) ), From 8156e79d279e04e4519bca2e72e050b17c2e7f2c Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 23 Jan 2025 16:36:49 +0200 Subject: [PATCH 47/55] add classes to align data --- .../components/most-popular-table.js | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/js/src/dashboard/components/most-popular-table.js b/packages/js/src/dashboard/components/most-popular-table.js index 704679d38a1..07a99120386 100644 --- a/packages/js/src/dashboard/components/most-popular-table.js +++ b/packages/js/src/dashboard/components/most-popular-table.js @@ -16,22 +16,28 @@ export const MostPopularTable = ( { data } ) => { return { __( "Landing page", "wordpress-seo" ) } - { __( "Clicks", "wordpress-seo" ) } - { __( "Impressions", "wordpress-seo" ) } - { __( "CTR", "wordpress-seo" ) } - { __( "Average position", "wordpress-seo" ) } + { __( "Clicks", "wordpress-seo" ) } + { __( "Impressions", "wordpress-seo" ) } + { __( "CTR", "wordpress-seo" ) } + + { __( "Average position", "wordpress-seo" ) } + - { __( "SEO score", "wordpress-seo" ) } +
+
+ { __( "SEO score", "wordpress-seo" ) } +
+
{ data.map( ( { subject, clicks, impressions, ctr, averagePosition, seoScore }, index ) => ( - { subject } - { clicks } - { impressions } - { ctr } - { averagePosition } + { subject } + { clicks } + { impressions } + { ctr } + { averagePosition } ) ) } From 370622f476b4d860b6ee7d7343e41965cf69e7b1 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 23 Jan 2025 16:37:17 +0200 Subject: [PATCH 48/55] update warnings threshold --- packages/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/package.json b/packages/js/package.json index 5f6d121ce15..8a3e08f4b7b 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "cd ../.. && wp-scripts build --config config/webpack/webpack.config.js", "test": "jest", - "lint": "eslint . --max-warnings=64" + "lint": "eslint . --max-warnings=63" }, "dependencies": { "@draft-js-plugins/mention": "^5.0.0", From 2c0be6a30cb59950a1b2e9fe87d05f8a3e979858 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 23 Jan 2025 16:47:07 +0200 Subject: [PATCH 49/55] change the table width --- packages/js/src/dashboard/components/table-widget.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/js/src/dashboard/components/table-widget.js b/packages/js/src/dashboard/components/table-widget.js index ea11ac99653..16591523369 100644 --- a/packages/js/src/dashboard/components/table-widget.js +++ b/packages/js/src/dashboard/components/table-widget.js @@ -27,7 +27,7 @@ const ScoreBullet = ( { score } ) => ( const TableHead = ( { children } ) => { return - { "" } + { "" } { children } ; @@ -44,7 +44,7 @@ const TableHead = ( { children } ) => { */ const TableRow = ( { children, index, key } ) => { return - { index + 1 }. + { index + 1 }. { children } ; }; @@ -59,7 +59,7 @@ const TableRow = ( { children, index, key } ) => { */ export const TableWidget = ( { title, children } ) => { return ( - + { title } From 075b19ed2a4f73934bc703c66144888e404c21b1 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 23 Jan 2025 16:58:02 +0200 Subject: [PATCH 50/55] fix styling with classes and fix space --- packages/ui-library/src/elements/table/index.js | 2 +- packages/ui-library/src/elements/table/style.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui-library/src/elements/table/index.js b/packages/ui-library/src/elements/table/index.js index d22c317f45b..a097dcc4954 100644 --- a/packages/ui-library/src/elements/table/index.js +++ b/packages/ui-library/src/elements/table/index.js @@ -88,7 +88,7 @@ Head.propTypes = { * @returns {JSX.Element} The element. */ const Body = ( { children, className = "", ...props } ) => ( - { children } + { children } ); Body.propTypes = { diff --git a/packages/ui-library/src/elements/table/style.css b/packages/ui-library/src/elements/table/style.css index 7c43cb7fb78..7a62d1b1424 100644 --- a/packages/ui-library/src/elements/table/style.css +++ b/packages/ui-library/src/elements/table/style.css @@ -42,7 +42,7 @@ table { @apply yst-divide-y yst-divide-slate-300; - th { + .yst-table-header { @apply yst-align-bottom; } tbody { From 2b1ffa2b5dc4a3b09282ca74dee7fdeb9bfe45a1 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 23 Jan 2025 17:02:03 +0200 Subject: [PATCH 51/55] fix spacing --- packages/ui-library/src/elements/table/style.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/ui-library/src/elements/table/style.css b/packages/ui-library/src/elements/table/style.css index 7a62d1b1424..ee5bef1f7c1 100644 --- a/packages/ui-library/src/elements/table/style.css +++ b/packages/ui-library/src/elements/table/style.css @@ -10,17 +10,17 @@ .yst-table-cell { @apply yst-px-3 yst-py-4 yst-text-sm yst-text-slate-600; - } + } } } - + .yst-table--default { @apply yst-rounded-lg yst-shadow yst-ring-1 yst-ring-black yst-ring-opacity-5; table { @apply yst-divide-y yst-divide-slate-300; - thead{ + thead { @apply yst-bg-slate-50; .yst-table-header { @@ -45,6 +45,7 @@ .yst-table-header { @apply yst-align-bottom; } + tbody { @apply yst-divide-y yst-divide-gray-200 yst-bg-white; } From fae50a9b1daee7ba4f08ae8a3845f5fd44a0a29e Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 23 Jan 2025 17:03:55 +0200 Subject: [PATCH 52/55] fix spacing --- packages/ui-library/src/elements/table/style.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui-library/src/elements/table/style.css b/packages/ui-library/src/elements/table/style.css index ee5bef1f7c1..8205fbc4e2f 100644 --- a/packages/ui-library/src/elements/table/style.css +++ b/packages/ui-library/src/elements/table/style.css @@ -7,7 +7,7 @@ .yst-table-header { @apply yst-px-3 yst-py-4 yst-text-start yst-text-sm yst-font-semibold yst-text-slate-900; } - + .yst-table-cell { @apply yst-px-3 yst-py-4 yst-text-sm yst-text-slate-600; } @@ -16,7 +16,7 @@ .yst-table--default { @apply yst-rounded-lg yst-shadow yst-ring-1 yst-ring-black yst-ring-opacity-5; - + table { @apply yst-divide-y yst-divide-slate-300; From 7fd66b3c37db667791362de27b314429006ef8ec Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 23 Jan 2025 17:05:49 +0200 Subject: [PATCH 53/55] remove key prop --- packages/js/src/dashboard/components/table-widget.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/js/src/dashboard/components/table-widget.js b/packages/js/src/dashboard/components/table-widget.js index 16591523369..f13d79d6f5a 100644 --- a/packages/js/src/dashboard/components/table-widget.js +++ b/packages/js/src/dashboard/components/table-widget.js @@ -38,12 +38,11 @@ const TableHead = ( { children } ) => { * * @param {children} children The row cells. * @param {number} index The row index. - * @param {string} key The row key. * * @returns {JSX.Element} The row element. */ -const TableRow = ( { children, index, key } ) => { - return +const TableRow = ( { children, index } ) => { + return { index + 1 }. { children } ; From 9e997822106be0e89ae1a83eb40b1c065b5141ac Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 23 Jan 2025 17:08:08 +0200 Subject: [PATCH 54/55] remove container class --- packages/js/src/dashboard/components/table-widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/src/dashboard/components/table-widget.js b/packages/js/src/dashboard/components/table-widget.js index f13d79d6f5a..fa258824b57 100644 --- a/packages/js/src/dashboard/components/table-widget.js +++ b/packages/js/src/dashboard/components/table-widget.js @@ -58,7 +58,7 @@ const TableRow = ( { children, index } ) => { */ export const TableWidget = ( { title, children } ) => { return ( - + { title } From 5ce72c4b8ac6063229561d8f08e1d4bf391cd383 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 24 Jan 2025 09:43:43 +0200 Subject: [PATCH 55/55] fix paddings and colors --- packages/js/src/dashboard/components/table-widget.js | 2 +- packages/ui-library/src/elements/table/style.css | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/js/src/dashboard/components/table-widget.js b/packages/js/src/dashboard/components/table-widget.js index fa258824b57..30893a90a4b 100644 --- a/packages/js/src/dashboard/components/table-widget.js +++ b/packages/js/src/dashboard/components/table-widget.js @@ -43,7 +43,7 @@ const TableHead = ( { children } ) => { */ const TableRow = ( { children, index } ) => { return - { index + 1 }. + { index + 1 }. { children } ; }; diff --git a/packages/ui-library/src/elements/table/style.css b/packages/ui-library/src/elements/table/style.css index 8205fbc4e2f..ea8851937c1 100644 --- a/packages/ui-library/src/elements/table/style.css +++ b/packages/ui-library/src/elements/table/style.css @@ -43,7 +43,11 @@ @apply yst-divide-y yst-divide-slate-300; .yst-table-header { - @apply yst-align-bottom; + @apply yst-align-bottom yst-py-2; + } + + .yst-table-cell { + @apply yst-py-2; } tbody {