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() {