Skip to content

Hide Site Goals widget fully with warning notice when no custom dimension data exists within the selected date range #12774

Closed

Description

@jimmymadon

Feature Description

When every event provider plugin for a Site Goals widget has been deactivated and the currently selected dashboard date range contains no conversion events for that widget's goal type, the widget's content should be replaced by a warning notice.

See the "Notice when Event Providers are deactivated and no events detected for the selected date range" section of the Site Goals Enhancements - Mini design doc and the Figma mock.

Acceptance Criteria

  • Each Site Goals widget is fully replaced with a warning notice when both of the following are true:
    • no event provider plugin for that widget is active and
    • the currently selected dashboard date range contains no conversion events for that widget's goal type.
  • Any conversion event for the goal type counts, i.e. whether or not Site Kit attributed it to a provider or form. The notice is about the widget having nothing left to report, so it applies equally to a widget in its original aggregated state or in the tabbed breakdown state.
  • When the notice renders it is the widget's only content — no tabs, tiles, badges or other notices — so the per-tab notice from Show warning notice in Site Goals widget when event provider plugin is deactivated #12773 cannot appear alongside it.
  • The notice is a warning notice with a single "Got it" button as per the Figma mock.
  • Clicking "Got it" removes that widget from the dashboard, for all users of the site, until the conditions below restore it.
    • If the other widget still qualifies, only the actioned widget is removed — the Site Goals area and the remaining widget stay visible.
    • If neither widget qualifies, the entire Site Goals area and its dashboard navigation chip are removed.
  • A widget may only be removed while no event provider for it is active. A request to remove a widget whose provider is still active is rejected, so the widget cannot be removed for everyone by calling the API directly.
  • Removal is never automatic. No elapsed detection window, date range change or page reload may remove a widget on its own; only the "Got it" click does.
  • Site Goals reappears on its own, with no admin action and no new setting, once a supported event provider is active again and events are detected.
  • The notice disappears and the widget's normal content returns whenever a condition stops being true — for example the provider is reactivated, or the date range is widened to cover events.

Implementation Brief

PHP

  • In includes/Modules/Analytics_4/Site_Goals_Site_Settings.php:
    • Add a remove_widget( $category ) method that reads the current value, filters $category out of activeWidgets and set()s the result, returning the updated settings. The existing merge() only ever unions activeWidgets (by design, so the cron never drops values), so it cannot be used to remove a category.
  • Create includes/Modules/Analytics_4/Datapoints/Remove_Site_Goals_Widget.php:
    • Extend Site_Goals_Settings_Datapoint. Removal is deliberately available to any dashboard user, including view-only ones: by the time the notice renders there is no data for the widget to show, so it is already effectively absent, and no destructive data change is involved. The daily cron restores activeWidgets on its own once events return.
    • Take site_goals_site_settings from the definition in the constructor, exactly as Get_Site_Goals_Settings does, plus context (the guard below needs it), following the precedent in Get_Report.
    • Accept a widget request param, validating it against Site_Goals_Site_Settings::ALLOWED_WIDGETS — throw Missing_Required_Param_Exception when absent and Invalid_Param_Exception when it is not an allowed slug.
    • Guard the removal server-side. Before removing anything, call ( new Conversion_Tracking( $this->context ) )->get_active_provider_categories() and return a WP_Error with a 400 status when $widget is in that list. The endpoint is reachable by any dashboard user, so without this a direct API request could remove the widget for everyone regardless of what the UI shows. get_active_provider_categories() returns the same 'lead' / 'ecommerce' slugs as ALLOWED_WIDGETS, so the check is a single in_array().
      • This guard is deliberately the same condition the cron uses in update_active_site_goals_widgets(), which makes the two impossible to fight: while a provider is active the removal is refused, and once it is inactive the cron will not re-add the widget either. There is no window in which repeated requests could outrun the cron.
      • The date range half of the criteria is deliberately not enforced here. The date range is user-supplied input, so it cannot act as a guard — any caller could satisfy it by choosing an empty range — and checking it would mean running an Analytics report on every removal request.
    • Call remove_widget() and return the site-wide settings (array( 'activeWidgets' => … )), which is all the JS store needs to reconcile its state.
  • In includes/Modules/Analytics_4.php:
    • Register 'POST:remove-site-goals-widget' in get_datapoint_definitions(), alongside the existing POST:save-site-goals-settings entry — the same definition array GET:site-goals-settings is given, plus 'context' => $this->context.
    • Save_Site_Goals_Settings writes per-user settings only — leave it untouched; activeWidgets must not become writable through it.

JS datastore

  • In assets/js/modules/analytics-4/datastore/site-goals-settings.ts:
    • Add a fetchRemoveSiteGoalsWidgetStore via createFetchStore, posting to modules/analytics-4/data/remove-site-goals-widget with { widget: category }.
    • Its reducerCallback merges the response's activeWidgets over state.siteGoalsSettings, mirroring how fetchSaveSiteGoalsSettingsStore merges the per-user fields while preserving the rest.
    • Add the removeSiteGoalsWidget( category ) action as a createValidatedAction, following saveSiteGoalsSettings: clear the previous action error, dispatch the fetch, call setErrorForAction on failure, and return { response, error }.
    • Add an isRemovingSiteGoalsWidget( state ) selector reading state.isFetchingRemoveSiteGoalsWidget, mirroring the existing isSavingSiteGoalsSettings.
    • No other selector changes are needed: isSiteGoalsWidgetActive() reads activeWidgets and isSiteGoalsWidgetRenderable() builds on it, so both flip to false once the category is removed.
  • No dashboard changes are required to hide the area or the chip. The widget registrations in assets/js/modules/analytics-4/widgets/index.js (analyticsOnlineStorePerformance / analyticsLeadGenerationPerformance) already gate isActive on isSiteGoalsWidgetRenderable(), and AREA_MAIN_DASHBOARD_SITE_GOALS_PRIMARY / CONTEXT_MAIN_DASHBOARD_SITE_GOALS are dropped from DashboardMainApp and useVisibleSections when no widget in the area is active.

"No events in the selected date range" check

  • Add a useSiteGoalsHasEventsInDateRange( goalType ) hook in assets/js/modules/analytics-4/components/site-goals/hooks/:
    • Build report options from select( CORE_USER ).getDateRangeDates( { compare: false } ) with metrics: [ { name: 'eventCount' } ], dimensionFilters: { eventName: { filterType: 'inListFilter', value: <goal type's events> } } and a dedicated reportID, resolving the events from CONVERSION_REPORTING_ECOMMERCE_EVENTS / CONVERSION_REPORTING_LEAD_EVENTS.
    • No dimensions are needed — the report's single totals row is the whole answer. Read it with isZeroReport() from @/js/modules/analytics-4/utils/is-zero-report.
    • Fetch through useInViewSelect, matching useSiteGoalsBreakdown. Return undefined while the report is loading or has errored, otherwise ! isZeroReport( report ).

Component

  • Create assets/js/modules/analytics-4/components/site-goals/components/SiteGoalsRemovalNotice.tsx, alongside the existing EventProviderDeactivatedNotice.tsx:
    • Props: goalType.
    • Render <Notice> appropriately as per the Figma.
    • Resolve the per-goal-type title and description through a co-located useSiteGoalsRemovalNoticeCopy( goalType ) hook, mirroring the useEventProviderDeactivatedNoticeCopy pattern that Show warning notice in Site Goals widget when event provider plugin is deactivated #12773 established.
    • onClick dispatches removeSiteGoalsWidget( goalType ), takes inProgress from isRemovingSiteGoalsWidget(), and fires a trackEvent( `${ viewContext }_site-goals-widget`, 'remove_widget', goalType ) event.

Widgets

  • In assets/js/modules/analytics-4/components/site-goals/widgets/OnlineStorePerformanceWidget.tsx and LeadGenerationPerformanceWidget.tsx:
    • Select hasActiveEcommerceEventProviders / hasActiveLeadEventProviders from CORE_SITE and call useSiteGoalsHasEventsInDateRange( GOAL_TYPES.ECOMMERCE | GOAL_TYPES.LEAD ).
    • Derive shouldShowRemovalNotice = hasActiveProviders === false && hasEventsInDateRange === false.
    • Return early if shouldShowRemovalNotice, rendering the <SiteGoalsRemovalNotice goalType={ … } />. Don't return or wrap the WidgetComponent as it adds widget styling we don't need.

Test Coverage

  • Add tests and stories for the new SiteGoalsRemovalNotice component and its copy hook, covering both goal types and the "Got it" click.
  • Add tests for the new hook that checks whether the selected date range has any events for a goal type, including its loading and error states.
  • Extend the site-goals-settings datastore tests to cover the new removal action and the resulting widget state.
  • Extend the OnlineStorePerformanceWidget and LeadGenerationPerformanceWidget tests and stories to cover when the notice replaces the widget and when it does not, in both the aggregated and tabbed breakdown states.
  • Extend the widget registration tests to confirm a removed widget, and the Site Goals area, stop rendering.
  • Add PHPUnit coverage for removing a widget from the site-wide Site Goals settings, for the new REST datapoint including its params, permissions and its refusal to remove a widget whose event provider is still active, and for the cron restoring a widget that was previously removed.

QA Brief

  • Use a site whose Analytics property has online store and lead events. Deactivate every online store plugin (WooCommerce, Easy Digital Downloads), and pick a date range with no online store events. Confirm a warning notice replaces the whole "Online store performance" widget and matches the Figma mock. The "Learn more" link sits inline at the end of the text, not on a new line as the mock shows, as agreed in Slack.
  • Reload with the page scrolled to the top, then scroll down to "Site goals". Confirm the "Online store performance" card shows its title and one grey loading block until the notice takes its place, not a bare grey block without the card.
  • Switch between date ranges with no online store events. Confirm the notice stays in each.
  • Narrow the browser to a phone width and confirm the notice stays readable, with nothing cut off.
  • Sign in as a view-only (Dashboard Sharing) user and confirm they see the same notice with its Got it button.
  • Widen the date range to one that holds online store events. Confirm the notice leaves and the widget shows its tiles.
  • Go back to a range with no online store events, activate an online store plugin and reload. Confirm the notice leaves and the "Online store performance" widget shows its content.
  • Deactivate the online store plugin again, run this in the console, then reload. Confirm the Site Goals intro modal reads See what brings in new leads, not Track your most valuable goals:
googlesitekit.data.dispatch( 'core/user' ).removeDismissedItems( 'site-goals-intro-modal-banner' );
  • Click Show me and confirm the tour holds two steps, with the first one pointing at the "Lead generation performance" widget.
  • Click Got it on the online store notice. Confirm only that notice leaves the page, and the "Lead generation performance" widget stays.
  • Sign in as the view-only user again and confirm the "Online store performance" widget is gone for them too.
  • Deactivate every form plugin. Confirm a matching notice replaces the "Lead generation performance" widget.
  • Run the console line above again and reload. Confirm the intro modal does not appear, since no Site Goals widget shows its content.
  • Click Got it on the lead generation notice, then reload. Confirm the dashboard no longer shows the "Site goals" section or its chip at the top.
  • Activate an online store plugin and run the daily conversion reporting cron. Reload and confirm the "Site goals" chip and the "Online store performance" widget come back, with the widget's content.
  • Confirm the intro modal now reads See what drives your sales, since its dismissal is still cleared.
  • With the online store plugin still active, run this in the console. Confirm the Network tab shows the remove-site-goals-widget request answered with 400, and the widget stays on the dashboard:
googlesitekit.api.set( 'modules', 'analytics-4', 'remove-site-goals-widget', { widget: 'ecommerce' } ).catch( () => {} );
  • Run this in the console to show the notice the way a page opened before the plugin came back would, then click Got it. Confirm the error under the notice reads Error: This Site Goals widget can’t be removed while a plugin that tracks its events is active. with no (Please try again.), and Got it stays clickable:
googlesitekit.data.dispatch( 'core/site' ).receiveSiteInfo( { ...googlesitekit.data.select( 'core/site' ).getSiteInfo(), hasActiveEcommerceEventProviders: false } );

Changelog entry

  • Add a warning notice that replaces a Site Goals widget when none of its event provider plugins is active and the selected date range has no conversion events, with an option to remove the widget.

Activity

  1. added
    P0High priority
    Next UpIssues to prioritize for definition
    on May 22, 2026
  2. self-assigned this
    on May 22, 2026
  3. changed the title [-]Hide Site Goals widget and show dismissible warning notice when no custom dimension data exists within the selected date range[/-] [+]Hide Site Goals widget fully with warning notice when no custom dimension data exists within the selected date range[/+] on May 22, 2026
  4. added
    Type: EnhancementImprovement of an existing feature
    and removed
    Next UpIssues to prioritize for definition
    on May 22, 2026
  5. removed their assignment
    on May 22, 2026
  6. eugene-manuilov commented on May 22, 2026

    @eugene-manuilov
    Collaborator

    AC ✔️

  7. removed their assignment
    on May 22, 2026
  8. 47 remaining items

  9. removed their assignment
    on Sep 29, 2026
  10. self-assigned this
    on Oct 6, 2026
  11. kelvinballoo commented on Oct 6, 2026

    @kelvinballoo
    Collaborator

    QA Update ⚠️

    THis is mostly verified good.
    I just have one clarification:

    • The notice appeared on the main admin dashboard and when dismissed it will dismiss on the view-only user's dashboard too. That's fair.
    • However, I noticed that if the view-only users dismisses the notice, it will be dismissed on the main dashboard as well.
    • I just feel it's a bit odd as a view-only user might potentially eliminate an admin's chance of seeing this notice. I feel an admin should at least be in the know of such things.

    What do you think?

  12. shervElmi commented on Oct 7, 2026

    @shervElmi
    Collaborator

    Hi @kelvinballoo, thanks for the QA update.

    What you saw is what the AC asks for. Clicking Got it removes the widget "for all users of the site." A view-only user can click it too. @jimmymadon gives the reasons in the comment on why any dashboard user can click Got it.

    But I agree that an administrator should see the notice. It says that reinstalling the plugin resumes the tracking. Only an administrator can reinstall a plugin. So I think only an administrator should be able to remove the widget. A view-only user would still see the notice, without the Got it button.

    @jimmymadon, what do you think?

  13. jimmymadon commented on Oct 7, 2026

    @jimmymadon
    CollaboratorAuthor

    @shervElmi @kelvinballoo

    Thanks for this - these are valid concerns. However, this is a very rare and unusual edge case. To prevent view-only users from actioning this adds more code to have separate permissions for this endpoint, so I don't think it is worth the extra effort and complexity here.

    Maybe a mid-way "hack" would be to simply hide the button on the front-end for view only users (but they can still hit the api endpoint). Additionally, we've made sure that any user can't simply hide the widget - the associated event provider plugin would really have to be deactivated and custom dimension data for the date range would be totally empty.

    Rather than introducing any anomaly, I would say this is fine the way it is because:

    • very rare edge case where a view only user will see the notice but not an admin
    • the site goals widget would have 0 data anyway and would be "useless"
    • there is already a warning notice that is shown when an event provider plugin is deactivated (even before this warning appears) - Issue Show warning notice in Site Goals widget when event provider plugin is deactivated #12773
    • there is a check to ensure the view only user simply cant remove the widget unless the event provider associated is deactivated (admin only action)
  14. removed their assignment
    on Oct 7, 2026
  15. shervElmi commented on Oct 7, 2026

    @shervElmi
    Collaborator

    Thanks @jimmymadon for the detailed clarification here.

    I see your point and I agree. @kelvinballoo I'm moving this one back to QA.

  16. removed their assignment
    on Oct 7, 2026
  17. kelvinballoo commented on Oct 8, 2026

    @kelvinballoo
    Collaborator

    QA Update ✅

    This was verified good. Moving to approval.

    • Online store has the notice, and it's responsive at different breakpoints.

    • Reloading the page doesn't make the notice go away. As long as other date ranges do not have data, the notice remains.

      Details
      12774.-.01.-.Online.store.has.the.notice.mov
      12774.-.01.-.reponsive.different.breakpoints.mov
      12774.-.02.-.reload.page.mov
      12774.-.03.-.different.ranges.mov
    • View only user works as expected. Once notice is dismissed, it's dismissed across the site for other users as well. This works from admin and also view-only user. The view-only case was discussed in this ticket and was deemed ok.

      Details Image
      12774.-.05.-.Dismissed.view-dahsbaord.admin.also.dismissed.not.ok.480p.mov
      12774.-.08.-.dismiss.in.admin.and.view.dismissed.as.well.mov
    • Reactivating the store plugin brought back the widget and the proper site goals modal appears as expected.

      Details
      12774.-.06.-.reactivating.store.plugin.ok.mov
      12774.-.07.-.bring.up.new.leads.modal.ok.mov
    • When forms plugins were deactivated, no modal appeared in that case. (No store modal as well).

      Details
      12774.-.10.-.forms.deactivated.-.no.moal.site.goal.ok.mov
      12774.-.11.-.no.chip.ok.mov
    • When we have no data in the last 28 days, the notice appears without the widget. When we change to 90 days where we have data, the widget appears as expected.

      Details
      12774.-.DC.retest.01.mov
      12774.-.DC.test.2.ok.90.day.and.28.day.ok.mov
  18. removed their assignment
    on Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0High priorityTeam SIssues for Squad 1Type: EnhancementImprovement of an existing feature

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions