google / google/site-kit-wp

Implement the shared `setup-flow` mechanism + the return to the hub

Open
#13,338 0 comments 0 reactions 1 assignee Claimed by @techanvil View on GitHub
P0 Team M Type: Enhancement
Dominant language
JavaScript
Stars
1.4k
Forks
384
Avg merge
4d 14h
Merged PRs (30d)
77

Description

## Feature Description

Most of the features the hub lists are set up in a flow Site Kit already has: the user leaves the hub for the service's own setup screens, and often for Google's, and comes back when it's done. The hub adds no setup UI of its own for these — clicking its call to action should start exactly the flow the user would get from Settings' **Connect more services** tab today.

Two things make that more than a link. The first is the handoff itself: the service has to be activated before the user can be sent into its setup. The second is the trip back. Every one of these flows finishes by sending the user to the dashboard, which is the right place when they started there — but a user who came from the hub to set up one feature has more they want to do, and dropping them on the dashboard makes them find their way back. So the hub records that it launched the setup, and a flow that finds such a record completes to the hub instead, on the tab the user started from.

This issue builds that shared path — the handoff, the record, and the return for every module that completes through Site Kit's common module-setup completion. It covers the features that are a plain service activation: Analytics, Ads, AdSense, PageSpeed Insights, Sign in with Google, and Reader Revenue Manager's full setup. The flows that finish somewhere of their own, and so have to consult the record themselves, are covered by #13340; the Reader Revenue Manager newsletter feature and Key Metrics have their own issues, #13345 and #13348. An activation that fails before the user leaves the hub is reported by #13339.

Everywhere else that starts one of these setups — a dashboard banner, Settings, anywhere that isn't the hub — is unaffected: the hub is an override, never a new default.

For reference, see the [`setup-flow` — navigate away to a Site Kit setup flow](https://docs.google.com/document/d/1sLWcimi6eZqbK4YXVCZtO0ZrfrDV7Ub218Hu_vpwmyI/edit?tab=t.0#heading=h.cmrx1in607w4) and [Returning to the hub](https://docs.google.com/document/d/1sLWcimi6eZqbK4YXVCZtO0ZrfrDV7Ub218Hu_vpwmyI/edit?tab=t.0#heading=h.yl5go7548bwn) sections in the design doc.

---------------

_Do not alter or remove anything below. The following sections will be managed by moderators only._

## Acceptance criteria

- Clicking a feature's call to action (#13322) where that feature is set up in an existing setup flow activates the feature's Google service and takes the user into that service's setup, exactly as Settings' **Connect more services** tab does today — for Analytics, Ads, AdSense, Sign in with Google, and Reader Revenue Manager (full setup).
- PageSpeed Insights is activated immediately, with nothing to configure, so the user stays on the hub. The confirmation they see is covered by #13346.
- Before the user is taken away, the hub records which feature they set out to set up and which tab they were on. The record belongs to the browser tab they left from, survives signing in to Google, and expires after an hour.
- Completing the setup returns the user to the hub, on the tab they started from, instead of the dashboard it goes to today. This covers every module that finishes through Site Kit's shared module-setup completion: AdSense, Sign in with Google, Reader Revenue Manager, and Ads where it doesn't use the PAX flow.
- Otherwise every flow keeps the destination it has today: where there's no record, where it's expired, or where it belongs to some other setup the user left unfinished rather than the one that just completed, the flow completes exactly where it does now.
- Landing back on the hub uses the record up, so the user is routed once: reloading the hub, or coming back to it later, doesn't route them again. A user who abandoned the setup and opens the hub afterwards simply gets it in its normal state.
- Where the service can't be activated, so the user never leaves the hub, the failure is reported on the feature's card — covered by #13339.
- Where the browser has no storage for the record, setup still works: the feature is set up and the user completes to the dashboard as today.
- The flows that finish on a screen of their own — Analytics' Key Metrics step, the Ads PAX flow, the Ad Blocking Recovery screen, the Key Metrics questionnaire, and Reader Revenue Manager's express route — are untouched here, and covered by #13340 and their own features' issues, #13348 and #13345.
- The success confirmation shown on return is covered by #13356: this issue lands the user on the right tab and shows nothing there.

## Implementation Brief

- [ ] In `assets/js/googlesitekit/feature-discovery/`, create `pending-setup.ts`:
- Define a cache key constant, `FEATURE_DISCOVERY_SETUP_CACHE_KEY = 'feature_discovery_setup'`, and export `FEATURE_DISCOVERY_TABS = { WHATS_NEW: 'whats-new', ALL_SERVICES: 'all-services' }` — the hub's hash-route segments (the tab components themselves land in `#13321`/`#13328`).
- Export `getCurrentFeatureDiscoveryTab()`: parses `global.location.hash` (`#/whats-new`, `#/all-services`) and falls back to `FEATURE_DISCOVERY_TABS.ALL_SERVICES` for anything else.
- Export `setPendingSetup( featureSlug: string, returnTab: string )`, wrapping `setItem( FEATURE_DISCOVERY_SETUP_CACHE_KEY, { featureSlug, returnTab } )` from `@/js/googlesitekit/api/cache`. Use the helper's default TTL (`HOUR_IN_SECONDS`) rather than passing one.
- Export `consumePendingSetup(): Promise<{ featureSlug: string; returnTab: string } | null>`: reads via `getItem()` then deletes via `deleteItem()`, returning `value` on a cache hit or `null` otherwise. This read-and-delete is what makes sure a record can only ever be consumed once.
- Export `getPendingSetupReturnURL( select: Select, returnTab: string )`, returning `` `${ select( CORE_SITE ).getAdminURL( 'googlesitekit-features' ) }#/${ returnTab }` ``.
- Model this on the existing `module_setup` cache item — written by `assets/js/components/settings/SetupModule.js` and cleared by `assets/js/components/setup/hooks/useFinishSetup.ts` — but keep it in its own file/key since it carries a feature slug and a tab, not a module slug.

- [ ] In `assets/js/googlesitekit/datastore/feature-discovery/features.ts`, implement the `setupFeature( slug )` generator action (started as a placeholder in `#13322`):
- Resolve the feature via `getFeature( slug )` and return `{}` unless `feature.setup.type === FEATURE_SETUP_TYPES.SETUP_FLOW` — the `background-toggle` and `in-place-panel` types are built by their own issues.
- Where `feature.setup.moduleSlug` is set (Analytics, Ads, AdSense, PageSpeed Insights, Sign in with Google, Reader Revenue Manager full setup):
- Dispatch `CORE_MODULES` `activateModule( moduleSlug )`.
- On `{ error }`, return it as-is and stop — surfacing it on the card is `#13339`'s job, out of scope here.
- On success, read `select( CORE_MODULES ).getModule( moduleSlug )?.SetupComponent`. If it's falsy (e.g. PageSpeed Insights — no setup screen registered), then stop: the module is already connected, the user stays on the hub, and nothing is written or navigated to.
- Otherwise, call `setPendingSetup( slug, getCurrentFeatureDiscoveryTab() )`, then dispatch `CORE_LOCATION` `navigateTo( response.moduleReauthURL )`.
- Otherwise, where `feature.setup.getSetupURL` is set (e.g. Ad Blocking Recovery; Key Metrics and the RRM newsletter form register their own descriptors in `#13348`/`#13345` but resolve through this same branch): resolve `setupURL = getSetupURL( select )` and, if defined, call `setPendingSetup( slug, getCurrentFeatureDiscoveryTab() )` then `navigateTo( setupURL )`.
- Import `CORE_MODULES` from `@/js/googlesitekit/modules/datastore/constants` and `CORE_LOCATION` from `@/js/googlesitekit/datastore/location/constants`; use the `commonActions.getRegistry()` / `commonActions.await()` pattern already used throughout the datastores (e.g. `assets/js/googlesitekit/modules/datastore/modules.js`).

- [ ] In `assets/js/components/setup/hooks/useFinishSetup.ts`, in the branch that runs when `finishSetup()` is called with no `redirectURL`, before building the dashboard `adminURL`:
- Call `consumePendingSetup()`.
- If it returns a record, read `registry.select( CORE_FEATURE_DISCOVERY ).getFeature( pendingSetup.featureSlug )?.setup?.moduleSlug` and confirm it matches this hook's `moduleSlug` — this stops a stale record from an abandoned, unrelated hub setup from hijacking a later, different completion.
- On a match, `navigateTo( getPendingSetupReturnURL( registry.select, pendingSetup.returnTab ) )` and return, skipping the dashboard `adminURL` build.
- On no record, or a mismatch, fall through to dashboard destination unchanged.
- Where browser storage isn't available, `consumePendingSetup()` resolves `null` (per `getItem()`'s existing cache-miss behaviour), so setup still completes — to the dashboard.

### Test Coverage

- Add unit tests for `pending-setup.ts`: `setPendingSetup`/`consumePendingSetup` round-tripping (including the read-and-delete/single-consumption behaviour) and `getPendingSetupReturnURL`'s output.
- Add/extend `features.test.ts` for `setupFeature()`: the `moduleSlug` activation success, error, and no-`SetupComponent` (PageSpeed Insights-style) paths, and the `getSetupURL` path.
- Extend `useFinishSetup.test.ts` to cover: returning to the hub on a matching pending-setup record, falling through to the dashboard on a mismatched, absent, or expired record, and confirming the existing `redirectURL` branch is unaffected.

## QA Brief

-

## Changelog entry

-

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.