Skip to content

Commit ef3677b

Browse files
authored
Merge pull request #12881 from google/enhancement/12789-list-services
List shared services on the splash screen
2 parents e511a74 + 1fe395c commit ef3677b

11 files changed

Lines changed: 290 additions & 10 deletions
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* Services component.
3+
*
4+
* Site Kit by Google, Copyright 2026 Google LLC
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
/**
20+
* External dependencies
21+
*/
22+
import type { FunctionComponent, SVGAttributes } from 'react';
23+
24+
/**
25+
* Internal dependencies
26+
*/
27+
import { Select, useSelect } from 'googlesitekit-data';
28+
import { CORE_USER } from '@/js/googlesitekit/datastore/user/constants';
29+
import { CORE_MODULES } from '@/js/googlesitekit/modules/datastore/constants';
30+
31+
type ServiceIcon = FunctionComponent< SVGAttributes< SVGElement > >;
32+
33+
interface Service {
34+
slug: string;
35+
name: string;
36+
order: number;
37+
Icon: ServiceIcon | null;
38+
}
39+
40+
export default function Services() {
41+
const services = useSelect( ( select: Select ): Service[] | undefined => {
42+
const viewableModules = select( CORE_USER ).getViewableModules();
43+
44+
if ( viewableModules === undefined ) {
45+
return undefined;
46+
}
47+
48+
return viewableModules
49+
.map( ( moduleSlug: string ): Service | null => {
50+
const module = select( CORE_MODULES ).getModule( moduleSlug );
51+
52+
if ( ! module ) {
53+
return null;
54+
}
55+
56+
const Icon = select( CORE_MODULES ).getModuleIcon( moduleSlug );
57+
58+
return {
59+
slug: moduleSlug,
60+
name: module.name,
61+
order: module.order,
62+
Icon,
63+
};
64+
} )
65+
.filter(
66+
( service: Service | null ): service is Service =>
67+
service !== null
68+
)
69+
.sort(
70+
( firstModule: Service, secondModule: Service ) =>
71+
firstModule.order - secondModule.order
72+
);
73+
}, [] );
74+
75+
if ( ! services?.length ) {
76+
return null;
77+
}
78+
79+
return (
80+
<ul className="googlesitekit-setup__services-list">
81+
{ services.map( ( { slug, name, Icon } ) => (
82+
<li
83+
key={ slug }
84+
className="googlesitekit-setup__services-list-item"
85+
>
86+
{ Icon && (
87+
<span className="googlesitekit-setup__services-list-item-icon">
88+
<Icon width={ 24 } height={ 24 } />
89+
</span>
90+
) }
91+
<span className="googlesitekit-setup__services-list-item-name">
92+
{ name }
93+
</span>
94+
</li>
95+
) ) }
96+
</ul>
97+
);
98+
}

assets/js/components/setup/SetupUsingProxyViewOnly/SplashViewOnlyContent.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { __ } from '@wordpress/i18n';
3434
import { Button } from 'googlesitekit-components';
3535
import Link from '@/js/components/Link';
3636
import OptIn from '@/js/components/OptIn';
37+
import Services from '@/js/components/setup/Services';
3738
import SplashScreenshotSVG from '@/js/components/setup/SetupUsingProxyWithSignIn/SetupFlowSVG';
3839
import Typography from '@/js/components/Typography';
3940
import {
@@ -92,6 +93,8 @@ export default function SplashViewOnlyContent( {
9293
) }
9394
</p>
9495

96+
<Services />
97+
9598
<OptIn />
9699

97100
<div className="googlesitekit-start-setup-wrap">

assets/js/components/setup/SetupUsingProxyViewOnly/index-setupFlowRefreshPhase4.stories.js

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,21 @@
2121
*/
2222
import { Provider as ViewContextProvider } from '@/js/components/Root/ViewContextContext';
2323
import { VIEW_CONTEXT_MAIN_DASHBOARD } from '@/js/googlesitekit/constants';
24-
import { CORE_USER } from '@/js/googlesitekit/datastore/user/constants';
25-
import { provideSiteConnection } from '@tests/js/utils';
24+
import {
25+
CORE_USER,
26+
PERMISSION_READ_SHARED_MODULE_DATA,
27+
} from '@/js/googlesitekit/datastore/user/constants';
28+
import { getMetaCapabilityPropertyName } from '@/js/googlesitekit/datastore/util/permissions';
29+
import { MODULE_SLUG_ANALYTICS_4 } from '@/js/modules/analytics-4/constants';
30+
import { MODULE_SLUG_SEARCH_CONSOLE } from '@/js/modules/search-console/constants';
31+
import AnalyticsIcon from '@/svg/graphics/analytics.svg';
32+
import SearchConsoleIcon from '@/svg/graphics/search-console.svg';
33+
import {
34+
provideModuleRegistrations,
35+
provideModules,
36+
provideSiteConnection,
37+
provideUserCapabilities,
38+
} from '@tests/js/utils';
2639
import WithRegistrySetup from '@tests/js/WithRegistrySetup';
2740
import SetupUsingProxyViewOnly from './index';
2841

@@ -50,6 +63,43 @@ export default {
5063
.dispatch( CORE_USER )
5164
.receiveGetTracking( { enabled: false } );
5265

66+
provideUserCapabilities( registry, {
67+
[ getMetaCapabilityPropertyName(
68+
PERMISSION_READ_SHARED_MODULE_DATA,
69+
MODULE_SLUG_SEARCH_CONSOLE
70+
) ]: true,
71+
[ getMetaCapabilityPropertyName(
72+
PERMISSION_READ_SHARED_MODULE_DATA,
73+
MODULE_SLUG_ANALYTICS_4
74+
) ]: true,
75+
} );
76+
77+
provideModules( registry, [
78+
{
79+
slug: MODULE_SLUG_SEARCH_CONSOLE,
80+
active: true,
81+
connected: true,
82+
shareable: true,
83+
},
84+
{
85+
slug: MODULE_SLUG_ANALYTICS_4,
86+
active: true,
87+
connected: true,
88+
shareable: true,
89+
},
90+
] );
91+
92+
provideModuleRegistrations( registry, [
93+
{
94+
slug: MODULE_SLUG_SEARCH_CONSOLE,
95+
Icon: SearchConsoleIcon,
96+
},
97+
{
98+
slug: MODULE_SLUG_ANALYTICS_4,
99+
Icon: AnalyticsIcon,
100+
},
101+
] );
102+
53103
// Call story-specific setup.
54104
if ( typeof args?.setupRegistry === 'function' ) {
55105
args.setupRegistry( registry );

assets/js/components/setup/SetupUsingProxyViewOnly/index.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import { SHARED_DASHBOARD_SPLASH_ITEM_KEY } from '@/js/components/setup/constant
2323
import { VIEW_CONTEXT_SPLASH } from '@/js/googlesitekit/constants';
2424
import { CORE_SITE } from '@/js/googlesitekit/datastore/site/constants';
2525
import { CORE_USER } from '@/js/googlesitekit/datastore/user/constants';
26+
import { CORE_MODULES } from '@/js/googlesitekit/modules/datastore/constants';
27+
import { MODULE_SLUG_ANALYTICS_4 } from '@/js/modules/analytics-4/constants';
28+
import { MODULE_SLUG_SEARCH_CONSOLE } from '@/js/modules/search-console/constants';
2629
import { mockLocation } from '@tests/js/mock-browser-utils';
2730
import { fireEvent, muteFetch, render, waitFor } from '@tests/js/test-utils';
2831
import {
@@ -178,6 +181,31 @@ describe( 'SetupUsingProxyViewOnly', () => {
178181

179182
describe( 'with the `setupFlowRefreshPhase4` feature flag enabled', () => {
180183
it( 'renders phase4 splash content and progress indicator', async () => {
184+
registry.dispatch( CORE_MODULES ).receiveGetModules(
185+
Object.values(
186+
registry.select( CORE_MODULES ).getModules()
187+
).map( ( module ) => {
188+
if (
189+
MODULE_SLUG_ANALYTICS_4 === module.slug ||
190+
MODULE_SLUG_SEARCH_CONSOLE === module.slug
191+
) {
192+
return {
193+
...module,
194+
active: true,
195+
connected: true,
196+
shareable: true,
197+
};
198+
}
199+
200+
return module;
201+
} )
202+
);
203+
204+
registry.dispatch( CORE_USER ).receiveGetCapabilities( {
205+
'googlesitekit_read_shared_module_data::["analytics-4"]': true,
206+
'googlesitekit_read_shared_module_data::["search-console"]': true,
207+
} );
208+
181209
const { container, getByText, getByRole, waitForRegistry } = render(
182210
<SetupUsingProxyViewOnly />,
183211
{
@@ -217,6 +245,17 @@ describe( 'SetupUsingProxyViewOnly', () => {
217245
expect(
218246
container.querySelector( '.googlesitekit-layout--rounded' )
219247
).toBeNull();
248+
249+
expect( getByText( 'Search Console' ) ).toBeInTheDocument();
250+
expect( getByText( 'Analytics' ) ).toBeInTheDocument();
251+
252+
expect(
253+
Array.from(
254+
container.querySelectorAll(
255+
'.googlesitekit-setup__services-list-item-name'
256+
)
257+
).map( ( element ) => element.textContent )
258+
).toEqual( [ 'Search Console', 'Analytics' ] );
220259
} );
221260

222261
it( 'should allow exiting the setup', async () => {

assets/js/components/setup/SetupUsingProxyWithSignIn/SplashContent.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { __, sprintf } from '@wordpress/i18n';
3333
*/
3434
import Link from '@/js/components/Link';
3535
import CompatibilityChecks from '@/js/components/setup/CompatibilityChecks';
36+
import Services from '@/js/components/setup/Services';
3637
import Typography from '@/js/components/Typography';
3738
import P from '@/js/components/Typography/P';
3839
import { DISCONNECTED_REASON_CONNECTED_URL_MISMATCH } from '@/js/googlesitekit/datastore/user/constants';
@@ -41,6 +42,7 @@ import {
4142
BREAKPOINT_TABLET,
4243
useBreakpoint,
4344
} from '@/js/hooks/useBreakpoint';
45+
import { useFeature } from '@/js/hooks/useFeature';
4446
import { Cell, Row } from '@/js/material-components';
4547
import SplashBackground from '@/svg/graphics/splash-graphic.svg';
4648
import AnalyticsOptIn from './AnalyticsOptIn';
@@ -68,6 +70,9 @@ export default function SplashContent( {
6870
const breakpoint = useBreakpoint();
6971
const isMobileOrTablet =
7072
breakpoint === BREAKPOINT_SMALL || breakpoint === BREAKPOINT_TABLET;
73+
const setupFlowRefreshPhase4Enabled = useFeature(
74+
'setupFlowRefreshPhase4'
75+
);
7176

7277
const cellDetailsProp = analyticsModuleActive
7378
? { smSize: 4, mdSize: 6, lgSize: 6 }
@@ -140,6 +145,9 @@ export default function SplashContent( {
140145
</P>
141146
) }
142147

148+
{ setupFlowRefreshPhase4Enabled &&
149+
analyticsModuleActive && <Services /> }
150+
143151
{ analyticsModuleAvailable && ! analyticsModuleActive && (
144152
<AnalyticsOptIn />
145153
) }

assets/js/components/setup/SetupUsingProxyWithSignIn/index-setupFlowRefresh.stories.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ import {
3636
import { getMetaCapabilityPropertyName } from '@/js/googlesitekit/datastore/util/permissions';
3737
import { MODULE_SLUG_ANALYTICS_4 } from '@/js/modules/analytics-4/constants';
3838
import { MODULE_SLUG_SEARCH_CONSOLE } from '@/js/modules/search-console/constants';
39+
import AnalyticsIcon from '@/svg/graphics/analytics.svg';
40+
import SearchConsoleIcon from '@/svg/graphics/search-console.svg';
3941
import {
42+
provideModuleRegistrations,
4043
provideModules,
4144
provideSiteConnection,
4245
provideSiteInfo,
@@ -294,16 +297,37 @@ SecondaryAdminWithSharedServices.args = {
294297
PERMISSION_READ_SHARED_MODULE_DATA,
295298
MODULE_SLUG_ANALYTICS_4
296299
) ]: true,
300+
[ getMetaCapabilityPropertyName(
301+
PERMISSION_READ_SHARED_MODULE_DATA,
302+
MODULE_SLUG_SEARCH_CONSOLE
303+
) ]: true,
297304
} );
298305

299306
provideModules( registry, [
307+
{
308+
slug: MODULE_SLUG_SEARCH_CONSOLE,
309+
active: true,
310+
connected: true,
311+
shareable: true,
312+
},
300313
{
301314
slug: MODULE_SLUG_ANALYTICS_4,
302315
active: true,
303316
connected: true,
304317
shareable: true,
305318
},
306319
] );
320+
321+
provideModuleRegistrations( registry, [
322+
{
323+
slug: MODULE_SLUG_SEARCH_CONSOLE,
324+
Icon: SearchConsoleIcon,
325+
},
326+
{
327+
slug: MODULE_SLUG_ANALYTICS_4,
328+
Icon: AnalyticsIcon,
329+
},
330+
] );
307331
},
308332
};
309333
SecondaryAdminWithSharedServices.parameters = {

0 commit comments

Comments
 (0)