Skip to content

Commit

Permalink
Merge pull request #2641 from woocommerce/feature/2539-show-promo
Browse files Browse the repository at this point in the history
Show promo for setting up Google Ads on the product feed tabs #2539
  • Loading branch information
joemcgill authored Oct 31, 2024
2 parents bd77902 + 616756f commit e7a0f64
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Flex, FlexItem } from '@wordpress/components';

/**
* Internal dependencies
*/
import AddPaidCampaignButton from '.~/components/paid-ads/add-paid-campaign-button';
import useMCProductStatistics from '.~/hooks/useMCProductStatistics';
import useAdsCampaigns from '.~/hooks/useAdsCampaigns';
import './index.scss';

const CreateCampaignNotice = () => {
const { data: products } = useMCProductStatistics();
const { loaded: campaignsLoaded, data: campaigns } = useAdsCampaigns();

if (
! products?.statistics?.active ||
! campaignsLoaded ||
campaigns?.length > 0

Check warning on line 22 in js/src/product-feed/product-statistics/create-campaign-notice/index.js

View check run for this annotation

Codecov / codecov/patch

js/src/product-feed/product-statistics/create-campaign-notice/index.js#L22

Added line #L22 was not covered by tests
) {
return null;
}

return (

Check warning on line 27 in js/src/product-feed/product-statistics/create-campaign-notice/index.js

View check run for this annotation

Codecov / codecov/patch

js/src/product-feed/product-statistics/create-campaign-notice/index.js#L27

Added line #L27 was not covered by tests
<Flex className="gla-ads-inline-notice">
<FlexItem>
<p>
{ __(
'You have approved products. Create a Google Ads campaign to reach more customers and drive more sales.',
'google-listings-and-ads'
) }
</p>
<AddPaidCampaignButton
isSmall={ false }
eventProps={ {
context: 'product-feed-overview-promotion',
} }
>
{ __( 'Create Campaign', 'google-listings-and-ads' ) }
</AddPaidCampaignButton>
</FlexItem>
</Flex>
);
};

export default CreateCampaignNotice;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.gla-ads-inline-notice {
background-color: #f0f6fc;
margin-bottom: $grid-unit-20;
padding: $grid-unit-20 $grid-unit-30;

p {
margin-top: 0;
}
}
3 changes: 3 additions & 0 deletions js/src/product-feed/product-statistics/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import SyncStatus from '.~/product-feed/product-statistics/status-box/sync-statu
import SyncProductStatistics from '.~/product-feed/product-statistics/status-box/sync-product-statistics';
import FeedStatus from '.~/product-feed/product-statistics/status-box/feed-status';
import AccountStatus from '.~/product-feed/product-statistics/status-box/account-status';
import CreateCampaignNotice from '.~/product-feed/product-statistics/create-campaign-notice';
import Text from '.~/components/app-text';
import AppSpinner from '.~/components/app-spinner';
import './index.scss';
Expand Down Expand Up @@ -133,7 +134,9 @@ const ProductStatistics = () => {
</SummaryList>
) }
</CardBody>

<CardFooter gap={ 0 }>
<CreateCampaignNotice />
<FeedStatus />
<SyncStatus />
<AccountStatus />
Expand Down
196 changes: 196 additions & 0 deletions tests/e2e/specs/product-feed/product-feed-campaign-notice.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/**
* External dependencies
*/
import { expect, test } from '@playwright/test';
/**
* Internal dependencies
*/
import {
clearOnboardedMerchant,
setOnboardedMerchant,
setCompletedAdsSetup,
clearCompletedAdsSetup,
} from '../../utils/api';
import ProductFeedPage from '../../utils/pages/product-feed';
import { LOAD_STATE } from '../../utils/constants';

test.use( { storageState: process.env.ADMINSTATE } );

test.describe.configure( { mode: 'serial' } );

/**
* @type {import('../../utils/pages/product-feed').default} productFeedPage
*/
let productFeedPage = null;

/**
* @type {import('@playwright/test').Page} page
*/
let page = null;

test.describe( 'Product Feed Page', () => {
test.beforeAll( async ( { browser } ) => {
page = await browser.newPage();
productFeedPage = new ProductFeedPage( page );
await Promise.all( [
productFeedPage.mockRequests(),
setOnboardedMerchant(),
] );
} );

test.afterAll( async () => {
await clearOnboardedMerchant();
await page.close();
} );

test.describe( 'No campaign', () => {
test.beforeAll( async () => {
await productFeedPage.fulfillAdsCampaignsRequest( [] );
} );

test( 'No active product and no campaign; Do not display campaign notice', async () => {
await productFeedPage.fulfillProductStatisticsRequest( {
timestamp: 1695011644,
statistics: {
active: 0,
expiring: 0,
pending: 0,
disapproved: 0,
not_synced: 1137,
},
scheduled_sync: 0,
loading: false,
} );

await productFeedPage.goto();
await expect(
page.getByRole( 'heading', { level: 1, name: 'Product Feed' } )
).toBeVisible();

await expect(
page.getByRole( 'heading', {
name: 'Overview',
} )
).toBeVisible();

await expect(
productFeedPage.getActiveProductValueElement()
).toBeVisible();

await expect(
productFeedPage.getActiveProductValueElement()
).toHaveText( /^0$/ );

await expect(
await productFeedPage.getCampaignNoticeSection()
).not.toBeVisible();
} );

test( 'Has active product but no campaign; Display campaign notice', async () => {
await productFeedPage.fulfillProductStatisticsRequest( {
timestamp: 1695011644,
statistics: {
active: 1,
expiring: 0,
pending: 0,
disapproved: 0,
not_synced: 1137,
},
scheduled_sync: 0,
loading: false,
} );

await productFeedPage.goto();

await expect(
productFeedPage.getActiveProductValueElement()
).toBeVisible();

await expect(
productFeedPage.getActiveProductValueElement()
).toHaveText( /^1$/ );

const noticeSection =
await productFeedPage.getCampaignNoticeSection();
const createCampaignButton =
productFeedPage.getInNoticeCreateCampaignButton();

await expect( noticeSection ).toBeVisible();
await expect( createCampaignButton ).toBeVisible();
await createCampaignButton.click();
await page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED );
await expect(
page.getByRole( 'heading', {
level: 1,
name: 'Set up your accounts',
} )
).toBeVisible();
} );
} );

test.describe( 'Has campaign', () => {
test.beforeAll( async () => {
await setCompletedAdsSetup();
await productFeedPage.fulfillAdsCampaignsRequest( [
{
id: 111111111,
name: 'Test Campaign',
status: 'enabled',
type: 'performance_max',
amount: 1,
country: 'US',
targeted_locations: [ 'US' ],
},
] );
} );

test.afterAll( async () => {
await clearCompletedAdsSetup();
await page.close();
} );

test( 'Has active product and a campaign; Do not display campaign notice', async () => {
await productFeedPage.goto();

await expect(
productFeedPage.getActiveProductValueElement()
).toBeVisible();

await expect(
productFeedPage.getActiveProductValueElement()
).toHaveText( /^1$/ );

await expect(
await productFeedPage.getCampaignNoticeSection()
).not.toBeVisible();
} );

test( 'Has campaign but no active product; Do not display campaign notice', async () => {
await productFeedPage.fulfillProductStatisticsRequest( {
timestamp: 1695011644,
statistics: {
active: 0,
expiring: 0,
pending: 0,
disapproved: 0,
not_synced: 1137,
},
scheduled_sync: 0,
loading: false,
} );
await productFeedPage.goto();

await expect(
productFeedPage.getActiveProductValueElement()
).toBeVisible();

await expect(
productFeedPage.getActiveProductValueElement()
).toHaveText( /^0$/ );

await expect(
await productFeedPage.getCampaignNoticeSection()
).not.toBeVisible();
} );
} );
} );
36 changes: 36 additions & 0 deletions tests/e2e/test-data/test-data.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,22 @@ function register_routes() {
],
],
);
register_rest_route(
'wc/v3',
'gla-test/ads-completed',
[
[
'methods' => 'POST',
'callback' => __NAMESPACE__ . '\set_ads_completed_at',
'permission_callback' => __NAMESPACE__ . '\permissions',
],
[
'methods' => 'DELETE',
'callback' => __NAMESPACE__ . '\clear_ads_completed_at',
'permission_callback' => __NAMESPACE__ . '\permissions',
],
],
);
register_rest_route(
'wc/v3',
'gla-test/notifications-ready',
Expand Down Expand Up @@ -100,6 +116,26 @@ function clear_onboarded_merchant() {
$options->delete( OptionsInterface::GOOGLE_CONNECTED );
}

/**
* Set the ADS_SETUP_COMPLETED_AT option.
*/
function set_ads_completed_at() {
/** @var OptionsInterface $options */
$options = woogle_get_container()->get( OptionsInterface::class );
$options->update(
OptionsInterface::ADS_SETUP_COMPLETED_AT,
1693215209
);
}

/**
* Clear a previously set ADS_SETUP_COMPLETED_AT option.
*/
function clear_ads_completed_at() {
/** @var OptionsInterface $options */
$options = woogle_get_container()->get( OptionsInterface::class );
$options->delete( OptionsInterface::ADS_SETUP_COMPLETED_AT );
}

/**
* Set the Ads Conversion Action to test values.
Expand Down
14 changes: 14 additions & 0 deletions tests/e2e/utils/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,20 @@ export async function clearOnboardedMerchant() {
await api().delete( 'gla-test/onboarded-merchant' );
}

/**
* Set Ads Completed At.
*/
export async function setCompletedAdsSetup() {
await api().post( 'gla-test/ads-completed' );
}

/**
* Clear Ads Completed At.
*/
export async function clearCompletedAdsSetup() {
await api().delete( 'gla-test/ads-completed' );
}

/**
* Set Notifications Ready.
*/
Expand Down
Loading

0 comments on commit e7a0f64

Please sign in to comment.