Skip to content

Commit a6869f4

Browse files
authored
Merge pull request #12084 from google/enhancement/11960-fix-connect-analytics-banner
Update PUE Analytics notice to resume setup and add external link icon.
2 parents 8d2cd60 + 0bc8ce0 commit a6869f4

4 files changed

Lines changed: 162 additions & 20 deletions

File tree

assets/js/components/email-reporting/notices/SetupAnalyticsNotice.js

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { CORE_SITE } from '@/js/googlesitekit/datastore/site/constants';
3737
import { CORE_MODULES } from '@/js/googlesitekit/modules/datastore/constants';
3838
import { MODULE_SLUG_ANALYTICS_4 } from '@/js/modules/analytics-4/constants';
3939
import useActivateModuleCallback from '@/js/hooks/useActivateModuleCallback';
40+
import useCompleteModuleActivationCallback from '@/js/hooks/useCompleteModuleActivationCallback';
4041
import Link from '@/js/components/Link';
4142
import useViewOnly from '@/js/hooks/useViewOnly';
4243

@@ -52,6 +53,10 @@ export default function SetupAnalyticsNotice() {
5253

5354
const isViewOnly = useViewOnly();
5455

56+
const isAnalyticsActive = useSelect( ( select ) =>
57+
select( CORE_MODULES ).isModuleActive( MODULE_SLUG_ANALYTICS_4 )
58+
);
59+
5560
const isAnalyticsConnected = useSelect( ( select ) =>
5661
select( CORE_MODULES ).isModuleConnected( MODULE_SLUG_ANALYTICS_4 )
5762
);
@@ -72,10 +77,23 @@ export default function SetupAnalyticsNotice() {
7277
MODULE_SLUG_ANALYTICS_4
7378
);
7479

80+
const completeModuleActivation = useCompleteModuleActivationCallback(
81+
MODULE_SLUG_ANALYTICS_4
82+
);
83+
84+
// If Analytics is already active but not connected, skip activation
85+
// and go directly to the setup flow.
86+
const onClickCallback = isAnalyticsActive
87+
? completeModuleActivation
88+
: activateAnalytics;
89+
7590
const handleCTAClick = useCallback( () => {
91+
if ( ! onClickCallback ) {
92+
return;
93+
}
7694
setInProgress( true );
77-
activateAnalytics();
78-
}, [ activateAnalytics ] );
95+
onClickCallback();
96+
}, [ onClickCallback ] );
7997

8098
const handleDismiss = useCallback( async () => {
8199
await dismissItem(
@@ -97,6 +115,10 @@ export default function SetupAnalyticsNotice() {
97115
return null;
98116
}
99117

118+
const ctaLabel = isAnalyticsActive
119+
? __( 'Complete setup', 'google-site-kit' )
120+
: __( 'Connect Analytics', 'google-site-kit' );
121+
100122
return (
101123
<Notice
102124
type={ TYPES.NEW }
@@ -110,17 +132,11 @@ export default function SetupAnalyticsNotice() {
110132
'google-site-kit'
111133
),
112134
{
113-
a: (
114-
<Link
115-
href={ learnMoreLink }
116-
external
117-
hideExternalIndicator
118-
/>
119-
),
135+
a: <Link href={ learnMoreLink } external />,
120136
}
121137
) }
122138
ctaButton={ {
123-
label: __( 'Connect Analytics', 'google-site-kit' ),
139+
label: ctaLabel,
124140
inProgress,
125141
disabled: inProgress,
126142
onClick: handleCTAClick,

assets/js/components/email-reporting/notices/SetupAnalyticsNotice.test.js

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
/**
2020
* External dependencies
2121
*/
22-
import { waitFor } from '@testing-library/react';
22+
import { waitFor, act } from '@testing-library/react';
2323

2424
/**
2525
* Internal dependencies
@@ -33,6 +33,7 @@ import {
3333
provideUserAuthentication,
3434
provideModuleRegistrations,
3535
provideSiteInfo,
36+
waitForDefaultTimeouts,
3637
} from '../../../../../tests/js/test-utils';
3738
import { CORE_USER } from '@/js/googlesitekit/datastore/user/constants';
3839
import { MODULE_SLUG_ANALYTICS_4 } from '@/js/modules/analytics-4/constants';
@@ -122,7 +123,29 @@ describe( 'SetupAnalyticsNotice', () => {
122123
);
123124
} );
124125

125-
it( 'dismisses the notice when "Got it" is clicked', async () => {
126+
it( 'dismisses the notice when "Maybe later" is clicked', async () => {
127+
// Create a fresh registry without module registrations to avoid
128+
// async resolver state updates that cause act() warnings.
129+
registry = createTestRegistry();
130+
provideSiteInfo( registry );
131+
provideUserAuthentication( registry );
132+
provideUserCapabilities( registry );
133+
provideModules( registry, [
134+
{
135+
slug: MODULE_SLUG_ANALYTICS_4,
136+
active: false,
137+
connected: false,
138+
disconnectedAt: false,
139+
},
140+
] );
141+
142+
// Note: Intentionally not calling `provideModuleRegistrations` here
143+
// to prevent the Analytics 4 store resolver from running.
144+
registry.dispatch( CORE_USER ).receiveGetDismissedItems( [] );
145+
registry.dispatch( CORE_SITE ).receiveGetEmailReportingSettings( {
146+
enabled: true,
147+
} );
148+
126149
fetchMock.getOnce( fetchGetDismissedItems, { body: [] } );
127150
fetchMock.postOnce( fetchDismissItem, {
128151
body: [ EMAIL_REPORTING_SETUP_ANALYTICS_NOTICE_DISMISSED_ITEM ],
@@ -171,4 +194,98 @@ describe( 'SetupAnalyticsNotice', () => {
171194

172195
expect( container ).toBeEmptyDOMElement();
173196
} );
197+
198+
it( 'shows spinner and disabled state while the activation is in progress', async () => {
199+
const moduleActivationEndpoint = RegExp(
200+
'google-site-kit/v1/core/modules/data/activation'
201+
);
202+
203+
const userAuthenticationEndpoint = RegExp(
204+
'^/google-site-kit/v1/core/user/data/authentication'
205+
);
206+
207+
fetchMock.getOnce( userAuthenticationEndpoint, {
208+
body: { needsReauthentication: false },
209+
} );
210+
211+
// Use a never-resolving promise to keep the activation in progress.
212+
fetchMock.postOnce( moduleActivationEndpoint, new Promise( () => {} ) );
213+
214+
const { getByRole } = render( <SetupAnalyticsNotice />, {
215+
registry,
216+
} );
217+
218+
const ctaButton = getByRole( 'button', {
219+
name: /connect analytics/i,
220+
} );
221+
222+
fireEvent.click( ctaButton );
223+
224+
// Wait for the spinner to appear.
225+
await waitFor( () => {
226+
expect( ctaButton ).toHaveAttribute( 'disabled' );
227+
} );
228+
229+
// Verify the button contains the spinner (via the CSS class).
230+
expect( ctaButton ).toHaveClass(
231+
'googlesitekit-notice__cta--spinner__running'
232+
);
233+
} );
234+
235+
it( 'shows "Complete setup" CTA and skips activation when Analytics is active but not connected', async () => {
236+
// Create a fresh registry with Analytics active but not connected.
237+
registry = createTestRegistry();
238+
provideSiteInfo( registry );
239+
provideUserAuthentication( registry );
240+
provideUserCapabilities( registry );
241+
provideModules( registry, [
242+
{
243+
slug: MODULE_SLUG_ANALYTICS_4,
244+
active: true,
245+
connected: false,
246+
disconnectedAt: false,
247+
},
248+
] );
249+
provideModuleRegistrations( registry );
250+
251+
registry.dispatch( CORE_USER ).receiveGetDismissedItems( [] );
252+
registry.dispatch( CORE_SITE ).receiveGetEmailReportingSettings( {
253+
enabled: true,
254+
} );
255+
256+
const moduleActivationEndpoint = RegExp(
257+
'google-site-kit/v1/core/modules/data/activation'
258+
);
259+
260+
const { getByRole } = render( <SetupAnalyticsNotice />, {
261+
registry,
262+
} );
263+
264+
// Verify the CTA label is "Complete setup".
265+
const ctaButton = getByRole( 'button', {
266+
name: /complete setup/i,
267+
} );
268+
269+
expect( ctaButton ).toBeInTheDocument();
270+
271+
await act( async () => {
272+
fireEvent.click( ctaButton );
273+
await waitForDefaultTimeouts();
274+
} );
275+
276+
// Verify that the activation endpoint was NOT called.
277+
expect( fetchMock ).not.toHaveFetched( moduleActivationEndpoint );
278+
} );
279+
280+
it( 'shows external link indicator on the "Learn more" link', () => {
281+
const { container } = render( <SetupAnalyticsNotice />, {
282+
registry,
283+
} );
284+
285+
// Find the link by looking for the external icon SVG.
286+
const externalIcon = container.querySelector(
287+
'.googlesitekit-cta-link svg'
288+
);
289+
expect( externalIcon ).toBeInTheDocument();
290+
} );
174291
} );

assets/js/hooks/useCompleteModuleActivationCallback.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,15 @@ export default function useCompleteModuleActivationCallback( moduleSlug ) {
4646
const canManageOptions = useSelect( ( select ) =>
4747
select( CORE_USER ).hasCapability( PERMISSION_MANAGE_OPTIONS )
4848
);
49-
const moduleStoreName = useSelect( ( select ) =>
50-
select( CORE_MODULES ).getModuleStoreName( moduleSlug )
51-
);
52-
const adminReauthURL = useSelect( ( select ) =>
53-
select( moduleStoreName )?.getAdminReauthURL()
54-
);
49+
const adminReauthURL = useSelect( ( select ) => {
50+
const moduleStoreName =
51+
select( CORE_MODULES ).getModuleStoreName( moduleSlug );
52+
53+
if ( ! moduleStoreName ) {
54+
return undefined;
55+
}
56+
return select( moduleStoreName )?.getAdminReauthURL();
57+
} );
5558
const { navigateTo } = useDispatch( CORE_LOCATION );
5659

5760
const completeModuleActivationCallback = useCallback(

assets/js/modules/analytics-4/components/audience-segmentation/settings/SettingsCardVisitorGroups/index.test.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,16 @@ describe( 'SettingsCardVisitorGroups', () => {
158158
audienceSegmentationSetupCompletedBy: null,
159159
} );
160160

161+
// Set available audiences directly to ensure the selector returns
162+
// the data without triggering a network request during save.
163+
registry
164+
.dispatch( MODULES_ANALYTICS_4 )
165+
.setAvailableAudiences( availableAudiences );
166+
161167
fetchMock.post( audienceSettingsEndpoint, ( url, opts ) => {
162168
const { data } = JSON.parse( opts.body );
163-
// Return the same settings passed to the API.
164-
return { body: data, status: 200 };
169+
// Return the settings object directly (not wrapped in data).
170+
return { body: data.settings, status: 200 };
165171
} );
166172
} );
167173

0 commit comments

Comments
 (0)