Skip to content

Commit

Permalink
Merge pull request #2180 from woocommerce/update/onboarding-require-g…
Browse files Browse the repository at this point in the history
…oogle-ads-connection

Change to require Google Ads connection during the onboarding
  • Loading branch information
eason9487 authored Dec 15, 2023
2 parents 185f3f8 + 368bb0c commit 813f555
Show file tree
Hide file tree
Showing 13 changed files with 382 additions and 208 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default function MockupSearch( { product } ) {
</ScaledText>
<Placeholder stroke="thinner" width="79" color="blue" />
</div>
<Flex justfy="space-between" align="stretch">
<Flex align="stretch">
<div className="gla-ads-mockup__search-card-placeholders">
<Placeholder width="100" />
<Placeholder width="97" />
Expand Down
11 changes: 11 additions & 0 deletions js/src/hooks/useGoogleAdsAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useCallback } from '@wordpress/element';
*/
import { STORE_KEY } from '.~/data/constants';
import { useAppDispatch } from '.~/data';
import { GOOGLE_ADS_ACCOUNT_STATUS } from '.~/constants';
import useGoogleAccount from './useGoogleAccount';

const googleAdsAccountSelector = 'getGoogleAdsAccount';
Expand Down Expand Up @@ -36,13 +37,23 @@ const useGoogleAdsAccount = () => {
googleAdsAccountSelector
);

// The "incomplete" status means there is a connected account but billing is not yet set
// so it's considered as `true`.
// The main reason for not using a naming like `isGoogleAdsConnected` here is to make
// a slight distinction from the "connected" status.
const hasGoogleAdsConnection = [
GOOGLE_ADS_ACCOUNT_STATUS.CONNECTED,
GOOGLE_ADS_ACCOUNT_STATUS.INCOMPLETE,
].includes( acc?.status );

return {
googleAdsAccount: acc,
isResolving: isResolvingGoogleAdsAccount,
refetchGoogleAdsAccount,
hasFinishedResolution: selector.hasFinishedResolution(
googleAdsAccountSelector
),
hasGoogleAdsConnection,
};
},
[ google, isResolving, refetchGoogleAdsAccount ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import AppDocumentationLink from '.~/components/app-documentation-link';
import CampaignPreview from '.~/components/paid-ads/campaign-preview';
import './paid-ads-features-section.scss';

function FeatureList() {
function FeatureList( { hideBudgetContent } ) {
const featuresItems = [
{
Icon: GridiconCheckmark,
Expand All @@ -24,22 +24,27 @@ function FeatureList() {
'google-listings-and-ads'
),
},
{
Icon: GridiconCheckmark,
content: __(
'Set a daily budget, and only pay when someone clicks.',
'google-listings-and-ads'
),
},
{
Icon: GridiconGift,
content: __(
'Claim $500 in ads credit when you spend your first $500 with Google Ads. Terms and conditions apply.',
'google-listings-and-ads'
),
},
];

if ( ! hideBudgetContent ) {
featuresItems.push(
{
Icon: GridiconCheckmark,
content: __(
'Set a daily budget, and only pay when someone clicks.',
'google-listings-and-ads'
),
},
{
Icon: GridiconGift,
content: __(
'Claim $500 in ads credit when you spend your first $500 with Google Ads. Terms and conditions apply.',
'google-listings-and-ads'
),
}
);
}

return (
<div className="gla-paid-ads-features-section__feature-list">
{ featuresItems.map( ( { Icon, content }, idx ) => (
Expand All @@ -61,11 +66,13 @@ function FeatureList() {
* for the next actions: skip or continue the paid ads setup.
*
* @param {Object} props React props.
* @param {boolean} props.hideBudgetContent Whether to hide the content about the ad budget.
* @param {boolean} props.hideFooterButtons Whether to hide the buttons at the card footer.
* @param {JSX.Element} props.skipButton Button to skip paid ads setup.
* @param {JSX.Element} props.continueButton Button to continue paid ads setup.
*/
export default function PaidAdsFeaturesSection( {
hideBudgetContent,
hideFooterButtons,
skipButton,
continueButton,
Expand Down Expand Up @@ -120,7 +127,9 @@ export default function PaidAdsFeaturesSection( {
'google-listings-and-ads'
) }
</div>
<FeatureList />
<FeatureList
hideBudgetContent={ hideBudgetContent }
/>
<cite className="gla-paid-ads-features-section__cite">
{ __(
'Source: Google Internal Data, July 2020',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@ import { Form } from '@woocommerce/components';
import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount';
import useTargetAudienceFinalCountryCodes from '.~/hooks/useTargetAudienceFinalCountryCodes';
import useGoogleAdsAccountBillingStatus from '.~/hooks/useGoogleAdsAccountBillingStatus';
import GoogleAdsAccountSection from './google-ads-account-section';
import AudienceSection from '.~/components/paid-ads/audience-section';
import BudgetSection from '.~/components/paid-ads/budget-section';
import BillingCard from '.~/components/paid-ads/billing-card';
import SpinnerCard from '.~/components/spinner-card';
import Section from '.~/wcdl/section';
import validateCampaign from '.~/components/paid-ads/validateCampaign';
import clientSession from './clientSession';
import {
GOOGLE_ADS_ACCOUNT_STATUS,
GOOGLE_ADS_BILLING_STATUS,
} from '.~/constants';
import { GOOGLE_ADS_BILLING_STATUS } from '.~/constants';

/**
* @typedef { import(".~/data/actions").CountryCode } CountryCode
Expand Down Expand Up @@ -77,7 +75,7 @@ function resolveInitialPaidAds( paidAds, targetAudience ) {
* @param {(onStatesReceived: PaidAdsData)=>void} props.onStatesReceived Callback to receive the data for setting up paid ads when initial and also when the audience, budget, and billing are updated.
*/
export default function PaidAdsSetupSections( { onStatesReceived } ) {
const { googleAdsAccount } = useGoogleAdsAccount();
const { hasGoogleAdsConnection } = useGoogleAdsAccount();
const { data: targetAudience } = useTargetAudienceFinalCountryCodes();
const { billingStatus } = useGoogleAdsAccountBillingStatus();

Expand Down Expand Up @@ -135,7 +133,11 @@ export default function PaidAdsSetupSections( { onStatesReceived } ) {
}, [ targetAudience ] );

if ( ! targetAudience || ! billingStatus ) {
return <GoogleAdsAccountSection />;
return (
<Section>
<SpinnerCard />
</Section>
);
}

const initialValues = {
Expand All @@ -153,16 +155,12 @@ export default function PaidAdsSetupSections( { onStatesReceived } ) {
>
{ ( formProps ) => {
const { countryCodes } = formProps.values;
const disabledAudience = ! [
GOOGLE_ADS_ACCOUNT_STATUS.CONNECTED,
GOOGLE_ADS_ACCOUNT_STATUS.INCOMPLETE,
].includes( googleAdsAccount?.status );
const disabledAudience = ! hasGoogleAdsConnection;
const disabledBudget =
disabledAudience || countryCodes.length === 0;

return (
<>
<GoogleAdsAccountSection />
<AudienceSection
formProps={ formProps }
disabled={ disabledAudience }
Expand Down
20 changes: 14 additions & 6 deletions js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import { noop, merge } from 'lodash';
*/
import useAdminUrl from '.~/hooks/useAdminUrl';
import useDispatchCoreNotices from '.~/hooks/useDispatchCoreNotices';
import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount';
import useAdsSetupCompleteCallback from '.~/hooks/useAdsSetupCompleteCallback';
import StepContent from '.~/components/stepper/step-content';
import StepContentHeader from '.~/components/stepper/step-content-header';
import StepContentFooter from '.~/components/stepper/step-content-footer';
import FaqsSection from '.~/components/paid-ads/faqs-section';
import AppButton from '.~/components/app-button';
import ProductFeedStatusSection from './product-feed-status-section';
import GoogleAdsAccountSection from './google-ads-account-section';
import PaidAdsFeaturesSection from './paid-ads-features-section';
import PaidAdsSetupSections from './paid-ads-setup-sections';
import { getProductFeedUrl } from '.~/utils/urls';
Expand Down Expand Up @@ -53,7 +55,7 @@ const ACTION_SKIP = 'skip-ads';
*
* @event gla_onboarding_complete_button_click
* @property {string} opened_paid_ads_setup Whether the paid ads setup is opened, e.g. 'yes', 'no'
* @property {string} google_ads_account_status The connection status of merchant's Google Ads addcount, e.g. 'unknown', 'connected', 'disconnected', 'incomplete'
* @property {string} google_ads_account_status The connection status of merchant's Google Ads addcount, e.g. 'connected', 'disconnected', 'incomplete'
* @property {string} billing_method_status aaa, The status of billing method of merchant's Google Ads addcount e.g. 'unknown', 'pending', 'approved', 'cancelled'
* @property {string} campaign_form_validation Whether the entered paid campaign form data are valid, e.g. 'unknown', 'valid', 'invalid'
*/
Expand All @@ -69,6 +71,7 @@ const ACTION_SKIP = 'skip-ads';
export default function SetupPaidAds() {
const adminUrl = useAdminUrl();
const { createNotice } = useDispatchCoreNotices();
const { googleAdsAccount, hasGoogleAdsConnection } = useGoogleAdsAccount();
const [ handleSetupComplete ] = useAdsSetupCompleteCallback();
const [ showPaidAdsSetup, setShowPaidAdsSetup ] = useState( () =>
clientSession.getShowPaidAdsSetup( false )
Expand Down Expand Up @@ -123,31 +126,32 @@ export default function SetupPaidAds() {
function createSkipButton( text ) {
const eventProps = {
opened_paid_ads_setup: 'no',
google_ads_account_status: 'unknown',
google_ads_account_status: googleAdsAccount?.status,
billing_method_status: 'unknown',
campaign_form_validation: 'unknown',
};

if ( showPaidAdsSetup ) {
const selector = select( STORE_KEY );
const account = selector.getGoogleAdsAccount();
const billing = selector.getGoogleAdsAccountBillingStatus();

merge( eventProps, {
opened_paid_ads_setup: 'yes',
google_ads_account_status: account?.status,
billing_method_status: billing?.status,
campaign_form_validation: paidAds.isValid ? 'valid' : 'invalid',
} );
}

const disabledSkip =
completing === ACTION_COMPLETE || ! hasGoogleAdsConnection;

return (
<AppButton
isTertiary
data-action={ ACTION_SKIP }
text={ text }
loading={ completing === ACTION_SKIP }
disabled={ completing === ACTION_COMPLETE }
disabled={ disabledSkip }
onClick={ finishOnboardingSetup }
eventName="gla_onboarding_complete_button_click"
eventProps={ eventProps }
Expand All @@ -168,8 +172,12 @@ export default function SetupPaidAds() {
) }
/>
<ProductFeedStatusSection />
<GoogleAdsAccountSection />
<PaidAdsFeaturesSection
hideFooterButtons={ showPaidAdsSetup }
hideBudgetContent={ ! hasGoogleAdsConnection }
hideFooterButtons={
! hasGoogleAdsConnection || showPaidAdsSetup
}
skipButton={ createSkipButton(
__( 'Skip this step for now', 'google-listings-and-ads' )
) }
Expand Down
14 changes: 7 additions & 7 deletions src/Tracking/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ A modal is open
- [`ReviewRequest`](../../js/src/product-feed/review-request/index.js#L31) with `context: REQUEST_REVIEW`
- [`SubmissionSuccessGuide`](../../js/src/product-feed/submission-success-guide/index.js#L155) with `context: GUIDE_NAMES.SUBMISSION_SUCCESS`

### [`gla_onboarding_complete_button_click`](../../js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js#L47)
### [`gla_onboarding_complete_button_click`](../../js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js#L49)
Clicking on the skip paid ads button to complete the onboarding flow.
The 'unknown' value of properties may means:
- the paid ads setup is not opened
Expand All @@ -581,26 +581,26 @@ Clicking on the skip paid ads button to complete the onboarding flow.
| name | type | description |
| ---- | ---- | ----------- |
`opened_paid_ads_setup` | `string` | Whether the paid ads setup is opened, e.g. 'yes', 'no'
`google_ads_account_status` | `string` | The connection status of merchant's Google Ads addcount, e.g. 'unknown', 'connected', 'disconnected', 'incomplete'
`google_ads_account_status` | `string` | The connection status of merchant's Google Ads addcount, e.g. 'connected', 'disconnected', 'incomplete'
`billing_method_status` | `string` | aaa, The status of billing method of merchant's Google Ads addcount e.g. 'unknown', 'pending', 'approved', 'cancelled'
`campaign_form_validation` | `string` | Whether the entered paid campaign form data are valid, e.g. 'unknown', 'valid', 'invalid'
#### Emitters
- [`exports`](../../js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js#L69)
- [`exports`](../../js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js#L71)

### [`gla_onboarding_complete_with_paid_ads_button_click`](../../js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js#L39)
### [`gla_onboarding_complete_with_paid_ads_button_click`](../../js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js#L41)
Clicking on the "Complete setup" button to complete the onboarding flow with paid ads.
#### Properties
| name | type | description |
| ---- | ---- | ----------- |
`budget` | `number` | The budget for the campaign
`audiences` | `string` | The targeted audiences for the campaign
#### Emitters
- [`exports`](../../js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js#L69)
- [`exports`](../../js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js#L71)

### [`gla_onboarding_open_paid_ads_setup_button_click`](../../js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js#L33)
### [`gla_onboarding_open_paid_ads_setup_button_click`](../../js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js#L35)
Clicking on the "Create a paid ad campaign" button to open the paid ads setup in the onboarding flow.
#### Emitters
- [`exports`](../../js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js#L69)
- [`exports`](../../js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js#L71)

### [`gla_paid_campaign_step`](../../js/src/utils/recordEvent.js#L122)
Triggered when moving to another step during creating/editing a campaign.
Expand Down
12 changes: 1 addition & 11 deletions tests/e2e/specs/add-paid-campaigns/add-paid-campaigns.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,16 +248,6 @@ test.describe( 'Set up Ads account', () => {
test.describe( 'Create your paid campaign', () => {
test.beforeAll( async () => {
setupBudgetPage = new SetupBudgetPage( page );
await setupBudgetPage.fulfillBudgetRecommendation( {
currency: 'USD',
recommendations: [
{
country: 'US',
daily_budget_low: 5,
daily_budget_high: 15,
},
],
} );
} );

test( 'Continue to create paid campaign', async () => {
Expand Down Expand Up @@ -360,7 +350,7 @@ test.describe( 'Set up Ads account', () => {

test( 'Budget Recommendation', async () => {
await expect(
page.getByText( 'set a daily budget of 5 to 15 USD' )
page.getByText( 'set a daily budget of 15 USD' )
).toBeVisible();
} );
} );
Expand Down
Loading

0 comments on commit 813f555

Please sign in to comment.