diff --git a/changelog.txt b/changelog.txt index d966cd171b..7901ac5a41 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,9 @@ *** WooCommerce Google Listings and Ads Changelog *** += 2.5.0 - 2023-07-18 = +* Tweak - Add Tip with information with Campaign assets are imported. +* Tweak - Provide more detailed error reasons when unable to complete site verification for the Google Merchant Center account being connected in the onboarding flow. + = 2.4.11 - 2023-07-11 = * Add - Client name and plugin version to requests. * Dev - Enable unit testing for PHP 8.1. diff --git a/google-listings-and-ads.php b/google-listings-and-ads.php index da6c467db6..4fcc3c288b 100644 --- a/google-listings-and-ads.php +++ b/google-listings-and-ads.php @@ -3,7 +3,7 @@ * Plugin Name: Google Listings and Ads * Plugin URL: https://wordpress.org/plugins/google-listings-and-ads/ * Description: Native integration with Google that allows merchants to easily display their products across Google’s network. - * Version: 2.4.11 + * Version: 2.5.0 * Author: WooCommerce * Author URI: https://woocommerce.com/ * Text Domain: google-listings-and-ads @@ -30,7 +30,7 @@ defined( 'ABSPATH' ) || exit; -define( 'WC_GLA_VERSION', '2.4.11' ); // WRCS: DEFINED_VERSION. +define( 'WC_GLA_VERSION', '2.5.0' ); // WRCS: DEFINED_VERSION. define( 'WC_GLA_MIN_PHP_VER', '7.4' ); define( 'WC_GLA_MIN_WC_VER', '6.9' ); diff --git a/js/src/components/paid-ads/asset-group/asset-group-section.js b/js/src/components/paid-ads/asset-group/asset-group-section.js index 3979ed1fb4..d836655806 100644 --- a/js/src/components/paid-ads/asset-group/asset-group-section.js +++ b/js/src/components/paid-ads/asset-group/asset-group-section.js @@ -3,6 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { createInterpolateElement } from '@wordpress/element'; +import { Tip } from '@wordpress/components'; /** * Internal dependencies @@ -24,6 +25,7 @@ import './asset-group-section.scss'; */ export default function AssetGroupSection() { const { adapter } = useAdaptiveFormContext(); + const showTip = adapter.hasImportedAssets; return (
+ { showTip && ( + + { __( + 'We auto-populated assets directly from your Final URL. We encourage you to edit or add more in order to best showcase your business.', + 'google-listings-and-ads' + ) } + + ) }
diff --git a/js/src/components/paid-ads/asset-group/asset-group-section.scss b/js/src/components/paid-ads/asset-group/asset-group-section.scss index a72a3c992c..63c487fdd4 100644 --- a/js/src/components/paid-ads/asset-group/asset-group-section.scss +++ b/js/src/components/paid-ads/asset-group/asset-group-section.scss @@ -5,4 +5,17 @@ font-size: $default-font-size; color: $gray-700; } + + .components-tip { + padding: $grid-unit-15 $grid-unit-20; + background: #f0f6fc; + border: $border-width solid #c5d9ed; + line-height: $gla-line-height-medium; + color: $gray-900; + + > svg { + fill: #007cba; + align-self: initial; + } + } } diff --git a/js/src/components/paid-ads/asset-group/asset-group-section.test.js b/js/src/components/paid-ads/asset-group/asset-group-section.test.js new file mode 100644 index 0000000000..e1c9f869a4 --- /dev/null +++ b/js/src/components/paid-ads/asset-group/asset-group-section.test.js @@ -0,0 +1,68 @@ +jest.mock( '.~/components/adaptive-form', () => ( { + useAdaptiveFormContext: jest + .fn() + .mockName( 'useAdaptiveFormContext' ) + .mockImplementation( () => { + return { + adapter: { + baseAssetGroup: { final_url: 'https://example.com' }, + hasImportedAssets: false, + isEmptyAssetEntityGroup: false, + resetAssetGroup: jest.fn(), + }, + }; + } ), +} ) ); + +/** + * External dependencies + */ +import '@testing-library/jest-dom'; +import { render, screen } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import AssetGroupSection from '.~/components/paid-ads/asset-group/asset-group-section'; +import { useAdaptiveFormContext } from '.~/components/adaptive-form'; + +jest.mock( '.~/components/paid-ads/asset-group/asset-group-card', () => + jest.fn( ( props ) =>
).mockName( 'AssetGroupCard' ) +); + +describe( 'AssetGroupSection', () => { + test( 'Component renders', () => { + render( ); + expect( + screen.getByText( /Add additional assets/i ) + ).toBeInTheDocument(); + } ); + + test( 'Component not showing Tip if there are no imported assets', () => { + render( ); + expect( + screen.queryByText( + 'We auto-populated assets directly from your Final URL. We encourage you to edit or add more in order to best showcase your business.' + ) + ).not.toBeInTheDocument(); + } ); + + test( 'Component showing Tip if there are imported assets', () => { + useAdaptiveFormContext.mockImplementation( () => { + return { + adapter: { + baseAssetGroup: { final_url: 'https://example.com' }, + hasImportedAssets: true, + isEmptyAssetEntityGroup: false, + resetAssetGroup: jest.fn(), + }, + }; + } ); + render( ); + expect( + screen.getByText( + 'We auto-populated assets directly from your Final URL. We encourage you to edit or add more in order to best showcase your business.' + ) + ).toBeInTheDocument(); + } ); +} ); diff --git a/js/src/components/paid-ads/campaign-assets-form.js b/js/src/components/paid-ads/campaign-assets-form.js index 758be01346..9cf587e2e4 100644 --- a/js/src/components/paid-ads/campaign-assets-form.js +++ b/js/src/components/paid-ads/campaign-assets-form.js @@ -77,6 +77,7 @@ export default function CampaignAssetsForm( { const [ baseAssetGroup, setBaseAssetGroup ] = useState( initialAssetGroup ); const [ validationRequestCount, setValidationRequestCount ] = useState( 0 ); + const [ hasImportedAssets, setHasImportedAssets ] = useState( false ); const extendAdapter = ( formContext ) => { const assetGroupErrors = validateAssetGroup( formContext.values ); @@ -90,13 +91,25 @@ export default function CampaignAssetsForm( { baseAssetGroup, validationRequestCount, assetGroupErrors, + /* + In order to show a Tip in the UI when assets are imported we created the hasImportedAssets + property. When the Final URL changes resetAssetGroup is called with the new Asset Group, + We check if any of the assets has been populated and update this property based on that. + */ + hasImportedAssets, isValidAssetGroup: Object.keys( assetGroupErrors ).length === 0, resetAssetGroup( assetGroup ) { const nextAssetGroup = assetGroup || initialAssetGroup; + let hasNonEmptyAssets = false; Object.keys( emptyAssetGroup ).forEach( ( key ) => { + if ( assetGroup && assetGroup[ key ]?.length ) { + hasNonEmptyAssets = true; + } formContext.setValue( key, nextAssetGroup[ key ] ); } ); + + setHasImportedAssets( hasNonEmptyAssets ); setBaseAssetGroup( nextAssetGroup ); setValidationRequestCount( 0 ); }, diff --git a/package-lock.json b/package-lock.json index e83371fff9..cf63cd5888 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "google-listings-and-ads", - "version": "2.4.11", + "version": "2.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 3096fce0bf..e2e6e66f23 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "google-listings-and-ads", "title": "Google Listings and Ads", - "version": "2.4.11", + "version": "2.5.0", "description": "google-listings-and-ads", "author": "Automattic", "license": "GPL-3.0-or-later", diff --git a/readme.txt b/readme.txt index 8afe59bfe8..6ce8a408dc 100644 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Requires at least: 5.9 Tested up to: 6.2 Requires PHP: 7.4 Requires PHP Architecture: 64 Bits -Stable tag: 2.4.11 +Stable tag: 2.5.0 License: GPLv3 License URI: https://www.gnu.org/licenses/gpl-3.0.html @@ -111,6 +111,10 @@ Yes, you can run both at the same time, and we recommend it! In the US, advertis == Changelog == += 2.5.0 - 2023-07-18 = +* Tweak - Add Tip with information with Campaign assets are imported. +* Tweak - Provide more detailed error reasons when unable to complete site verification for the Google Merchant Center account being connected in the onboarding flow. + = 2.4.11 - 2023-07-11 = * Add - Client name and plugin version to requests. * Dev - Enable unit testing for PHP 8.1. @@ -129,7 +133,4 @@ Yes, you can run both at the same time, and we recommend it! In the US, advertis = 2.4.10 - 2023-06-13 = * Tweak - WC 7.8 compatibility. -= 2.4.9 - 2023-06-08 = -* Fix - Prefix psr/http-client package. - [See changelog for all versions](https://raw.githubusercontent.com/woocommerce/google-listings-and-ads/trunk/changelog.txt). diff --git a/src/API/Google/SiteVerification.php b/src/API/Google/SiteVerification.php index b86612409f..96fc978bb5 100644 --- a/src/API/Google/SiteVerification.php +++ b/src/API/Google/SiteVerification.php @@ -3,6 +3,7 @@ namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Google; +use Automattic\WooCommerce\GoogleListingsAndAds\Exception\ExceptionWithResponseData; use Automattic\WooCommerce\GoogleListingsAndAds\Internal\ContainerAwareTrait; use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\ContainerAwareInterface; use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareInterface; @@ -27,6 +28,7 @@ class SiteVerification implements ContainerAwareInterface, OptionsAwareInterface { use ContainerAwareTrait; + use ExceptionTrait; use OptionsAwareTrait; use PluginHelper; @@ -94,7 +96,7 @@ public function verify_site( string $site_url ) { * @param string $identifier The URL of the site to verify (including protocol). * * @return string The meta tag to be used for verification. - * @throws Exception When unable to retrieve meta token. + * @throws ExceptionWithResponseData When unable to retrieve meta token. */ protected function get_token( string $identifier ): string { /** @var SiteVerificationService $service */ @@ -116,9 +118,15 @@ protected function get_token( string $identifier ): string { $response = $service->webResource->getToken( $post_body ); } catch ( GoogleServiceException $e ) { do_action( 'woocommerce_gla_sv_client_exception', $e, __METHOD__ ); - throw new Exception( - __( 'Unable to retrieve site verification token.', 'google-listings-and-ads' ), - $e->getCode() + + $errors = $this->get_exception_errors( $e ); + + throw new ExceptionWithResponseData( + /* translators: %s Error message */ + sprintf( __( 'Unable to retrieve site verification token: %s', 'google-listings-and-ads' ), reset( $errors ) ), + $e->getCode(), + null, + [ 'errors' => $errors ] ); } @@ -131,7 +139,7 @@ protected function get_token( string $identifier ): string { * * @param string $identifier The URL of the site to verify (including protocol). * - * @throws Exception When unable to verify token. + * @throws ExceptionWithResponseData When unable to verify token. */ protected function insert( string $identifier ) { /** @var SiteVerificationService $service */ @@ -152,9 +160,15 @@ protected function insert( string $identifier ) { $service->webResource->insert( self::VERIFICATION_METHOD, $post_body ); } catch ( GoogleServiceException $e ) { do_action( 'woocommerce_gla_sv_client_exception', $e, __METHOD__ ); - throw new Exception( - __( 'Unable to insert site verification.', 'google-listings-and-ads' ), - $e->getCode() + + $errors = $this->get_exception_errors( $e ); + + throw new ExceptionWithResponseData( + /* translators: %s Error message */ + sprintf( __( 'Unable to insert site verification: %s', 'google-listings-and-ads' ), reset( $errors ) ), + $e->getCode(), + null, + [ 'errors' => $errors ] ); } } diff --git a/tests/Unit/API/Google/SiteVerificationTest.php b/tests/Unit/API/Google/SiteVerificationTest.php index 6f508fdcbf..1e1af42502 100644 --- a/tests/Unit/API/Google/SiteVerificationTest.php +++ b/tests/Unit/API/Google/SiteVerificationTest.php @@ -4,8 +4,10 @@ namespace Automattic\WooCommerce\GoogleListingsAndAds\Tests\Unit\API\Google; use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\SiteVerification; +use Automattic\WooCommerce\GoogleListingsAndAds\Exception\ExceptionWithResponseData; use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface; use Automattic\WooCommerce\GoogleListingsAndAds\Tests\Framework\UnitTest; +use Automattic\WooCommerce\GoogleListingsAndAds\Tests\Tools\HelperTrait\MerchantTrait; use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\League\Container\Container; use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\Google\Service\Exception as GoogleServiceException; use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\Google\Service\SiteVerification as SiteVerificationService; @@ -23,6 +25,8 @@ */ class SiteVerificationTest extends UnitTest { + use MerchantTrait; + /** @var MockObject|OptionsInterface $options */ protected $options; @@ -71,18 +75,15 @@ public function test_verify_site_invalid_url() { public function test_verify_site_token_exception() { $this->verification_service->webResource ->method( 'getToken' ) - ->willThrowException( new GoogleServiceException( 'error', 400 ) ); - - try { - $this->verification->verify_site( $this->site_url ); - } catch ( Exception $e ) { - $this->assertEquals( 1, did_action( 'woocommerce_gla_site_verify_failure' ) ); - $this->assertEquals( 400, $e->getCode() ); - $this->assertEquals( - 'Unable to retrieve site verification token.', - $e->getMessage() - ); - } + ->willThrowException( $this->get_google_service_exception( 400, 'No available tokens' ) ); + + $this->expectException( ExceptionWithResponseData::class ); + $this->expectExceptionCode( 400 ); + $this->expectExceptionMessage( 'Unable to retrieve site verification token: No available tokens' ); + + $this->verification->verify_site( $this->site_url ); + + $this->assertEquals( 1, did_action( 'woocommerce_gla_site_verify_failure' ) ); } public function test_verify_site_insert_exception() { @@ -90,18 +91,15 @@ public function test_verify_site_insert_exception() { $this->verification_service->webResource ->method( 'insert' ) - ->willThrowException( new GoogleServiceException( 'error', 400 ) ); - - try { - $this->verification->verify_site( $this->site_url ); - } catch ( Exception $e ) { - $this->assertEquals( 1, did_action( 'woocommerce_gla_site_verify_failure' ) ); - $this->assertEquals( 400, $e->getCode() ); - $this->assertEquals( - 'Unable to insert site verification.', - $e->getMessage() - ); - } + ->willThrowException( $this->get_google_service_exception( 400, 'No necessary verification token.' ) ); + + $this->expectException( ExceptionWithResponseData::class ); + $this->expectExceptionCode( 400 ); + $this->expectExceptionMessage( 'Unable to insert site verification: No necessary verification token.' ); + + $this->verification->verify_site( $this->site_url ); + + $this->assertEquals( 1, did_action( 'woocommerce_gla_site_verify_failure' ) ); } public function test_verify_site() {