Implement in-place success detection & notice
- Dominant language
- JavaScript
- Stars
- 1.4k
- Forks
- 384
- Avg merge
- 4d 14h
- Merged PRs (30d)
- 77
Description
## Feature Description
Some of the hub's features are set up without the user ever leaving the page — switched on in the background, or configured in a panel that opens over the hub. When that happens the user needs to be told it worked, and then the card should go — it isn't something to add any more.
This issue adds that confirmation. It takes the feature's place in the list, says what the user now has, and can point them at where to see it or manage it. Dismissing it leaves the list without that feature, which is the end state either way — the next time they open the hub, the feature simply isn't listed.
What the hub can wait for differs by feature. Where the hub itself runs the setup — the features switched on in the background, and PageSpeed Insights, which is activated instantly and needs nothing configured — it knows the moment it succeeded. Where the user does the work in a panel, the hub isn't part of the saving, so it watches for the feature actually becoming set up and confirms when it does; a panel closed without saving anything confirms nothing.
The confirmation shown when a user comes *back* to the hub from a setup flow that took them away is a different placement, at the top of the list, and has its own issue (#13356).
For reference, see the [Success detection & removal](https://docs.google.com/document/d/1sLWcimi6eZqbK4YXVCZtO0ZrfrDV7Ub218Hu_vpwmyI/edit?tab=t.0#heading=h.hnxlpo1aczw) section in the design doc.
---------------
_Do not alter or remove anything below. The following sections will be managed by moderators only._
## Acceptance criteria
- When a feature is set up without the user leaving the hub, a success notice takes that feature's place in the list, in the position its card occupied, on either tab.
- What it says comes from the feature itself (#13247): a title, an optional second line, an optional call to action — which either takes the user somewhere, on the site or off it, or does something in place — and a dismiss labelled *"Got it"* unless the feature gives it another label.
- It's shown for the features switched on in the background — Enhanced measurement (#13341) and Visitor groups (#13344) — as soon as the setup succeeds.
- It's shown for PageSpeed Insights (#13338), which is activated on the spot with nothing to configure, so the user never leaves the hub.
- It's shown for the features configured in a panel over the hub (#13347) once what the user did there is saved — sharing their dashboard, or subscribing to email reports — and not before. Closing such a panel without setting the feature up shows nothing and leaves the card as it was.
- The PDF report shows no confirmation, since downloading a report doesn't set anything up (#13347).
- Dismissing the notice removes it, leaving the list without that feature. The card doesn't come back — the feature is set up.
- The notice belongs to the visit it appeared in: reloading the hub, or coming back to it later, shows neither the notice nor the card, the feature no longer being listed on either tab.
- A feature set up from the detail panel (#13330) is confirmed the same way: the panel closes and the notice takes the feature's place in the list behind it.
- The notice is built to serve both of the hub's success placements, so the one shown at the top of the list on return from a setup flow can use it when it's built.
## Implementation Brief
- [ ] In `assets/js/googlesitekit/datastore/feature-discovery/`, retain a feature that just became connected without leaving the hub, so it keeps its slot in the catalog selectors until the user dismisses it:
- In `constants.ts`, add `SUCCEEDED_FEATURES_UI_KEY = 'feature-discovery-succeeded-features'`.
- In `selectors.ts`, update `getAvailableFeatures` so a feature is also included when `isFeatureConnected( feature.slug )` is `true` but its slug is present in `select( CORE_UI ).getValue( SUCCEEDED_FEATURES_UI_KEY ) || []`.
- [ ] In `assets/js/components/feature-discovery/FeatureDiscoveryApp.tsx`, add the success-detection watcher inline:
- Read `{ slug: isFeatureConnected( slug ) }` for every `getFeatures()` entry via `useSelect`, and compare it against the previous render's map, using `usePrevious` from `@wordpress/compose`.
- In a `useEffect` keyed on that map, where a slug's value flips from `false` to `true`, append it to the `SUCCEEDED_FEATURES_UI_KEY` list via `useDispatch( CORE_UI ).setValue()`, skipping any slug already present in the list.
- Do nothing on the first render (no previous map yet) — this is what stops a feature that's already connected when the hub loads from being flagged.
- [ ] Create `assets/js/components/feature-discovery/FeatureSuccessNotice.tsx`:
- Props: `slug: string`, `onDismiss: () => void`.
- Read the feature via `select( CORE_FEATURE_DISCOVERY ).getFeature( slug )` and returns `null` if it has no `successNotice`.
- Resolve `successNotice.cta.getURL( select )` (if present) via `useSelect`.
- Render a bare `Notice` (modeled on `assets/js/components/ToastNotice.tsx`'s direct, non-notification-framework use of `Notice` — this isn't registered through `core/notifications`, so it doesn't use `NoticeNotification`):
- `type={ NOTICE_TYPES.SUCCESS }`, `title`, `description` from the descriptor.
- `ctaButton={ { label, onClick, href: resolved cta URL, external } }` when `successNotice.cta` is set.
- `dismissButton={ { label: successNotice.dismissLabel || __( 'Got it', 'google-site-kit' ), onClick: onDismiss } }`.
- [ ] Create `assets/js/components/feature-discovery/FeatureListItem.tsx` — the per-slug switch the goal groups/"What's new" list render instead of `FeatureCard` directly (#13320):
- Prop: `slug: string`.
- Read `isConnected = isFeatureConnected( slug )`.
- When `isConnected`, renders ``, with `onDismiss` removing `slug` from the `SUCCEEDED_FEATURES_UI_KEY` list (read the current list, filter it out, `setValue()` the result). Once removed, the slug also drops out of `getAvailableFeatures()`, so the item disappears from the list on next render — there's nothing else to clean up.
- Otherwise render ``.
- Because `getAvailableFeatures()` (and the selectors built on it) only ever include a connected feature while it's in `SUCCEEDED_FEATURES_UI_KEY`, this component can trust `isConnected` here to mean "just succeeded" without re-deriving that itself.
- [ ] In `assets/js/googlesitekit/datastore/feature-discovery/types.ts`, remove the `// TODO: Replace this...` comment above the `FeatureSuccessNotice` interface now that `FeatureSuccessNotice.tsx` mirrors it directly.
### Test Coverage
- Extend `selectors.test.ts`'s `getAvailableFeatures` coverage: a connected feature is excluded by default, included once its slug is set in the `SUCCEEDED_FEATURES_UI_KEY` `core/ui` value, and excluded again once removed.
- Add `FeatureSuccessNotice.test.tsx`: renders title/description/CTA/dismiss from the feature's `successNotice`, resolves `cta.getURL`, calls `onDismiss`, and renders nothing for a feature without a `successNotice`.
- Add `FeatureListItem.test.tsx`: renders `FeatureCard` while not connected, renders `FeatureSuccessNotice` once connected, and dismissing it clears the slug from the `core/ui` value.
## QA Brief
-
## Changelog entry
-
Contributor guide
Research direction
Start in assets/js/components/feature-discovery/FeatureDiscoveryApp.tsx and the selectors in assets/js/googlesitekit/datastore/feature-discovery/. Read ToastNotice.tsx and the feature-discovery types before creating FeatureSuccessNotice.tsx and FeatureListItem.tsx. Run the focused selector and component tests, then add the listed coverage so connected features show the success notice, dismiss correctly, and remain absent afterward.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, react, typescript
- Domain
- frontend, testing
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100