Skip to content

Enhancement/13404 site goals selling products key metrics - #13452

Merged
tofumatt merged 10 commits into
developfrom
enhancement/13404-site-goals-selling-products-key-metrics
Sep 11, 2026
Merged

tofumatt merged 10 commits into
developfrom
enhancement/13404-site-goals-selling-products-key-metrics

Conversation

@mehidi258

Copy link
Copy Markdown
Collaborator

Summary

Related issue(s):

PR Author Checklist

  • My code is tested and passes existing unit tests.
  • My code has an appropriate set of unit tests which all pass.
  • My code is backward-compatible with WordPress 5.2 and PHP 7.4.
  • My code follows the WordPress coding standards.
  • My code has proper inline documentation.
  • I have added a QA Brief on the issue linked above.
  • I have signed the Contributor License Agreement (see https://cla.developers.google.com/).

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

Code Reviewer Checklist

  • Run the code.
  • Ensure the acceptance criteria are satisfied.
  • Reassess the implementation with the IB.
  • Ensure no unrelated changes are included.
  • Ensure CI checks pass.
  • Check Storybook where applicable.
  • Ensure there is a QA Brief.
  • Ensure there are no unexpected significant changes to file sizes.

Merge Reviewer Checklist

  • Ensure the PR has the correct target branch.
  • Double-check that the PR is okay to be merged.
  • Ensure the corresponding issue has a ZenHub release assigned.
  • Add a changelog message to the issue.

… report logic with the Site Goals widget via a new reports module.
@github-actions

github-actions Bot commented Aug 26, 2026 •

Copy link
Copy Markdown

🤖 This comment is automatically updated by CI workflows. Each section is managed independently.

📚 Storybook for e8a829c:

  • Storybook has been deleted.

📦 Build files for e8a829c:

  • Build files have been deleted.

🎭 Playwright reports for e8a829c:

  • Reports have been deleted.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several new Key Metric widgets related to sales performance, including 'Total sales', 'Sales rate', 'Sales engagement rate', and various 'driving sales' breakdowns. The implementation adds new constants, widget configurations, and tile components, along with updated logic for report options and row mapping in the Analytics 4 module. I have reviewed the code and the provided feedback. The reviewer's comment regarding the duplication of primary ecommerce event resolution logic is valid and actionable, as it directly addresses the DRY principle outlined in the project's style guide. However, the comment regarding the columns constant definition is not actionable in the context of the provided code, as the component-specific columns depend on the component's scope and context, and moving them outside would not improve the code's quality or performance in a meaningful way.

Comment on lines +1862 to +1870
// `getPrimaryEcommerceEvent` derives from `getDetectedEvents`
// but has no resolver of its own, so resolve the detected
// events first and read the derived value once they're in.
await registry
.resolveSelect( MODULES_ANALYTICS_4 )
.getDetectedEvents();
const primaryEvent = registry
.select( MODULES_ANALYTICS_4 )
.getPrimaryEcommerceEvent();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for resolving the primary ecommerce event is duplicated across several new key metric tile definitions. This violates the DRY (Don't Repeat Yourself) principle mentioned in the repository's style guide (line 83).

Affected tiles:

  • KM_ANALYTICS_TOTAL_SALES (line 1861)
  • KM_ANALYTICS_SALES_RATE (line 1908)
  • KM_ANALYTICS_SALES_ENGAGEMENT_RATE (line 1971)
  • KM_ANALYTICS_TOP_TRAFFIC_CHANNELS_DRIVING_SALES_RATE (line 2028)
  • KM_ANALYTICS_SALES_BY_VISITOR_TYPE (line 2074)
  • KM_ANALYTICS_SALES_BY_COUNTRIES (line 2120)
  • KM_ANALYTICS_TOP_AUTHORS_DRIVING_SALES (line 2166)
  • KM_ANALYTICS_TOP_PAGES_DRIVING_SALES (line 2212)

To improve maintainability, you could extract this logic into a helper function. For example:

async function getPrimaryEcommerceEvent( registry ) {
    // `getPrimaryEcommerceEvent` derives from `getDetectedEvents`
    // but has no resolver of its own, so resolve the detected
    // events first and read the derived value once they're in.
    await registry
        .resolveSelect( MODULES_ANALYTICS_4 )
        .getDetectedEvents();
    return registry
        .select( MODULES_ANALYTICS_4 )
        .getPrimaryEcommerceEvent();
}

Then, you can use this helper in each getTileData function, for instance:

getTileData: createKeyMetricTileDataLoader(
    async ( dates, registry ) => {
        const primaryEvent = await getPrimaryEcommerceEvent( registry );
        if ( ! primaryEvent ) {
            return [];
        }
        // ...
    }
)

This will make the code cleaner and easier to maintain.

References
  1. Cross-file duplication / DRY — identical logic that should be extracted into a shared helper, trait, or utility. (link)

@github-actions

github-actions Bot commented Aug 26, 2026 •

Copy link
Copy Markdown

Size Change: +7.75 kB (+0.23%)

Total Size: 3.43 MB

📦 View Changed
Filename Size Change
dist/assets/js/googlesitekit-main-dashboard-********************.js 216 kB +1.08 kB (+0.5%)
dist/assets/js/googlesitekit-modules-analytics-4-********************.js 293 kB +3.06 kB (+1.06%)
dist/assets/js/googlesitekit-widgets-********************.js 182 kB +3.61 kB (+2.02%)
ℹ️ View Unchanged
Filename Size Change
dist/assets/blocks/reader-revenue-manager/block-editor-plugin/editor-styles.css 124 B 0 B
dist/assets/blocks/reader-revenue-manager/block-editor-plugin/editor-styles.js 0 B 0 B 🆕
dist/assets/blocks/reader-revenue-manager/block-editor-plugin/index.js 43.9 kB -29 B (-0.07%)
dist/assets/blocks/reader-revenue-manager/common/editor-styles.css 307 B 0 B
dist/assets/blocks/reader-revenue-manager/common/editor-styles.js 0 B 0 B 🆕
dist/assets/blocks/reader-revenue-manager/contribute-with-google/index.js 7.02 kB -24 B (-0.34%)
dist/assets/blocks/reader-revenue-manager/contribute-with-google/non-site-kit-user.js 6.23 kB 0 B
dist/assets/blocks/reader-revenue-manager/subscribe-with-google/index.js 7.03 kB -24 B (-0.34%)
dist/assets/blocks/reader-revenue-manager/subscribe-with-google/non-site-kit-user.js 6.23 kB 0 B
dist/assets/blocks/sign-in-with-google/editor-styles.css 84 B 0 B
dist/assets/blocks/sign-in-with-google/editor-styles.js 0 B 0 B 🆕
dist/assets/blocks/sign-in-with-google/index.js 18.5 kB 0 B
dist/assets/css/googlesitekit-admin-css-********************.min.css 75.8 kB 0 B
dist/assets/css/googlesitekit-adminbar-css-********************.min.css 12.7 kB 0 B
dist/assets/css/googlesitekit-authorize-application-css-********************.min.css 851 B 0 B
dist/assets/css/googlesitekit-wp-dashboard-css-********************.min.css 9.09 kB 0 B
dist/assets/js/20-********************.js 1.03 kB 0 B
dist/assets/js/22-********************.js 2.41 kB 0 B
dist/assets/js/212-********************.js 2.26 kB 0 B
dist/assets/js/315-********************.js 3.08 kB 0 B
dist/assets/js/397-********************.js 477 kB 0 B
dist/assets/js/492-********************.js 970 B 0 B
dist/assets/js/603-********************.js 52.7 kB 0 B
dist/assets/js/741-********************.js 3.84 kB 0 B
dist/assets/js/792-********************.js 101 kB 0 B
dist/assets/js/959-********************.js 3 kB 0 B
dist/assets/js/analytics-advanced-tracking-********************.js 428 B 0 B
dist/assets/js/googlesitekit-activation-********************.js 30.4 kB +126 B (+0.42%)
dist/assets/js/googlesitekit-ad-blocking-recovery-********************.js 67.7 kB +122 B (+0.18%)
dist/assets/js/googlesitekit-admin-pointers-tracking-********************.js 5.93 kB 0 B
dist/assets/js/googlesitekit-adminbar-********************.js 42.2 kB +136 B (+0.32%)
dist/assets/js/googlesitekit-api-********************.js 8.78 kB +124 B (+1.43%)
dist/assets/js/googlesitekit-block-tracking-********************.js 6.13 kB 0 B
dist/assets/js/googlesitekit-components-********************.js 6.86 kB 0 B
dist/assets/js/googlesitekit-consent-mode-********************.js 26 kB 0 B
dist/assets/js/googlesitekit-data-********************.js 1.83 kB 0 B
dist/assets/js/googlesitekit-datastore-forms-********************.js 7.77 kB 0 B
dist/assets/js/googlesitekit-datastore-location-********************.js 1.6 kB 0 B
dist/assets/js/googlesitekit-datastore-pdf-********************.js 1.22 kB 0 B
dist/assets/js/googlesitekit-datastore-site-********************.js 20.5 kB +124 B (+0.61%)
dist/assets/js/googlesitekit-datastore-ui-********************.js 7.94 kB 0 B
dist/assets/js/googlesitekit-datastore-user-********************.js 25.1 kB +557 B (+2.27%)
dist/assets/js/googlesitekit-entity-dashboard-********************.js 85 kB +132 B (+0.16%)
dist/assets/js/googlesitekit-events-provider-contact-form-7-********************.js 2.36 kB 0 B
dist/assets/js/googlesitekit-events-provider-content-events-********************.js 3.5 kB 0 B
dist/assets/js/googlesitekit-events-provider-easy-digital-downloads-********************.js 1.14 kB 0 B
dist/assets/js/googlesitekit-events-provider-mailchimp-********************.js 2.34 kB 0 B
dist/assets/js/googlesitekit-events-provider-ninja-forms-********************.js 2.3 kB 0 B
dist/assets/js/googlesitekit-events-provider-optin-monster-********************.js 2.23 kB 0 B
dist/assets/js/googlesitekit-events-provider-popup-maker-********************.js 2.47 kB 0 B
dist/assets/js/googlesitekit-events-provider-woocommerce-********************.js 1.14 kB 0 B
dist/assets/js/googlesitekit-events-provider-wpforms-********************.js 2.45 kB 0 B
dist/assets/js/googlesitekit-feature-discovery-********************.js 10.8 kB +123 B (+1.16%)
dist/assets/js/googlesitekit-features-********************.js 24 kB +112 B (+0.47%)
dist/assets/js/googlesitekit-i18n-********************.js 4.43 kB 0 B
dist/assets/js/googlesitekit-key-metrics-setup-********************.js 62.9 kB +119 B (+0.19%)
dist/assets/js/googlesitekit-metric-selection-********************.js 66.8 kB +647 B (+0.98%)
dist/assets/js/googlesitekit-modules-********************.js 29.4 kB +123 B (+0.42%)
dist/assets/js/googlesitekit-modules-ads-********************.js 52 kB +127 B (+0.25%)
dist/assets/js/googlesitekit-modules-adsense-********************.js 164 kB +876 B (+0.54%)
dist/assets/js/googlesitekit-modules-pagespeed-insights-********************.js 27.6 kB 0 B
dist/assets/js/googlesitekit-modules-reader-revenue-manager-********************.js 128 kB +121 B (+0.09%)
dist/assets/js/googlesitekit-modules-search-console-********************.js 77.2 kB +843 B (+1.1%)
dist/assets/js/googlesitekit-modules-sign-in-with-google-********************.js 37.4 kB +126 B (+0.34%)
dist/assets/js/googlesitekit-modules-tagmanager-********************.js 32.9 kB +124 B (+0.38%)
dist/assets/js/googlesitekit-notifications-********************.js 87.8 kB +124 B (+0.14%)
dist/assets/js/googlesitekit-polyfills-********************.js 227 B 0 B
dist/assets/js/googlesitekit-settings-********************.js 173 kB +703 B (+0.41%)
dist/assets/js/googlesitekit-splash-********************.js 93.8 kB +108 B (+0.12%)
dist/assets/js/googlesitekit-user-input-********************.js 58.7 kB +127 B (+0.22%)
dist/assets/js/googlesitekit-vendor-********************.js 314 kB 0 B
dist/assets/js/googlesitekit-vendor-lazy-pdf-********************.js 21.7 kB +122 B (+0.56%)
dist/assets/js/googlesitekit-wp-dashboard-********************.js 69.5 kB +123 B (+0.18%)
dist/assets/js/runtime-********************.js 1.94 kB +1 B (+0.05%)
dist/assets/js/sign-in-with-google-********************.js 1.14 kB 0 B

compressed-size-action

… IDs left over from the reports.ts extraction.

@zutigrm zutigrm left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @mehidi258 , few things to align with before it is ready for MR.

The refactor changes the percentage denominator for two of the eight drivers. TopAuthorsGoalDriver used to fetch a separate site-wide total report (analytics-4_site-goals_top-authors-total_${goalType}) and divide by it; TopTrafficChannelsGoalDriver used totalReport ?? sum of rows. Both now always divide by the sum of the returned rows.

The regenerated VRT reference is the proof — same fixture, same authors:
AuthorName1 Before: 30.5% After: 42.7%
AuthorName2 Before: 24.7% After: 34.6%
AuthorName3 Before: 16.2% After: 22.7%

It's a change of meaning, not rounding: "share of all sales" (summing to 71.4% of a site total of 1000) becomes "share of the top-N attributed rows" (summing to 100%). Let's restore the total report for these two drivers and have the Key Metric tiles fetch it too

We are also missing the test that IB mention which would catch the issue above "update the existing goal driver tests to exercise them through it, so the Site Goals widget is proven unchanged by the extraction", no existing goal-driver test was touched. TopAuthorsGoalDriver.test.tsx asserts no percentages, so all 58 tests pass with or without the denominator change.

Comment thread assets/js/modules/analytics-4/components/site-goals/goal-drivers/reports.ts Outdated
Comment thread assets/js/modules/analytics-4/components/site-goals/goal-drivers/reports.ts Outdated
Comment thread assets/js/modules/analytics-4/components/widgets/SalesByCountriesWidget.tsx Outdated
Comment thread assets/js/modules/analytics-4/components/widgets/SalesByCountriesWidget.tsx Outdated
Comment thread assets/js/modules/analytics-4/components/widgets/TotalSalesWidget.tsx Outdated

@tofumatt tofumatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the additional stories added here are adding a significant amount of bloat but aren't tested any new visual behaviours/UI elements.

All of the ADDED stories here can be removed; just the diffed ones should remain.

…eedback, and add missing percentage regression coverage for the Top traffic channels goal driver.
…ent/13404-site-goals-selling-products-key-metrics.
@mehidi258

Copy link
Copy Markdown
Collaborator Author

VRT failure is unrelated to this PR, pre-existing instability on develop.

@zutigrm zutigrm left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Passing it to @tofumatt to check his part

@tofumatt tofumatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of my comments here apply to all of the components in question, so please have a look through and address the comments in both the files with comments and apply them to similar files as well. 🙂

Please be sure to answer any questions and not just implement changes. I'm curious about the thought process here when asking, so I'd like to understand the original decisions made here even if we do want to change something.

} );

describe( 'getKeyMetricsConversionEventWidgets', () => {
it( 'includes the Selling products Key Metric tiles under purchase', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test spec is difficult to understand. It reads like all of these tiles are related to "Selling products" under a "Purchase" heading, but "purchase" is an event. How is it "under purchase"? Can you explain/reword this test spec so it more clearly/explicitly defines what's being tested?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworded to 'includes the "Selling products" Key Metrics widgets in the list of widgets mapped to the "purchase" conversion event'.

Comment on lines +79 to +83
function getDates() {
return registry
.select( CORE_USER )
.getDateRangeDates( { compare: true } );
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conversely, this is borderline overkill, but I'm fine if it stays…

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored to get rid of this now.

Comment on lines +107 to +113
[
'.googlesitekit-km-widget-tile__loading',
'.googlesitekit-km-widget-tile__loading-header',
'.googlesitekit-km-widget-tile__loading-body',
].forEach( ( selector ) => {
expect( container.querySelector( selector ) ).toBeInTheDocument();
} );

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more lines and less readable than just writing the selectors in the expectations:

Suggested change
[
'.googlesitekit-km-widget-tile__loading',
'.googlesitekit-km-widget-tile__loading-header',
'.googlesitekit-km-widget-tile__loading-body',
].forEach( ( selector ) => {
expect( container.querySelector( selector ) ).toBeInTheDocument();
} );
expect( container.querySelector( '.googlesitekit-km-widget-tile__loading' ) ).toBeInTheDocument();
expect( container.querySelector( '.googlesitekit-km-widget-tile__loading-header' ) ).toBeInTheDocument();
expect( container.querySelector( '.googlesitekit-km-widget-tile__loading-body' ) ).toBeInTheDocument();

But additionally—testing for the header and body is redundant. I suspect this is AI-generated; please audit test assertions like this because:

Suggested change
[
'.googlesitekit-km-widget-tile__loading',
'.googlesitekit-km-widget-tile__loading-header',
'.googlesitekit-km-widget-tile__loading-body',
].forEach( ( selector ) => {
expect( container.querySelector( selector ) ).toBeInTheDocument();
} );
expect( container.querySelector( '.googlesitekit-km-widget-tile__loading' ) ).toBeInTheDocument();

is better. The MetricTileLoader always outputs those other selectors, so we don't need to test/check for them or its internal layout. Knowing the top-level loading selector is here is enough.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied your suggestion.

Comment on lines +167 to +174
const metricElement = container.querySelector(
'.googlesitekit-km-widget-tile__metric'
);
expect( metricElement ).toBeInTheDocument();
expect(
// eslint-disable-next-line sitekit/acronym-case
within( metricElement as HTMLElement ).getByText( '0%' )
).toBeInTheDocument();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems better to cast this on definition. (Same elsewhere.)

Suggested change
const metricElement = container.querySelector(
'.googlesitekit-km-widget-tile__metric'
);
expect( metricElement ).toBeInTheDocument();
expect(
// eslint-disable-next-line sitekit/acronym-case
within( metricElement as HTMLElement ).getByText( '0%' )
).toBeInTheDocument();
const metricElement = container.querySelector(
'.googlesitekit-km-widget-tile__metric'
// eslint-disable-next-line sitekit/acronym-case
) as HTMLElement;
expect( metricElement ).toBeInTheDocument();
expect(
within( metricElement ).getByText( '0%' )
).toBeInTheDocument();

Comment on lines +47 to +49
* Builds a fake `select` for exercising a Key Metric tile's display gates
* without a full registry, mirroring the real selectors' "some events
* detected" / "every dimension available" combining logic.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you create this instead of supplying data to the registry and calling the selectors like we do in all other tests?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, Thanks for the flag, It was AI generated i didn't catch it 😔


const tooltipContent = documentationURL ? (
<Fragment>
{ infoTooltip }{ ' ' }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be localised properly with createInterpolateElement, not a non-translated whitespace character.

See:

<span className="googlesitekit-error-retry-text">
{ createInterpolateElement(
__(
'Retry didn’t work? <HelpLink />',
'google-site-kit'
),
{
HelpLink: (
<Link
href={ errorTroubleshootingLinkURL }
external
hideExternalIndicator
>
{ __(
'Get help',
'google-site-kit'
) }
</Link>
),
}
) }
</span>
as an example.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not add entirely new files as JS. Please check the contents of the PR before submitting for things like this; we should only add new files as TypeScript.

Comment on lines +63 to +65
// The widget-scoped components below are wrapped in `forwardRef`, whose
// narrow inferred prop type otherwise fails structural checks against the
// looser prop types TS widget components declare for them (e.g. `WidgetNull`).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

narrow inferred prop type otherwise fails structural checks against the looser prop types TS widget components declare for them

Can you rephrase this? That's quite technical and difficult to make sense of.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment on lines +73 to +74
// Scope widget-specific components to the widget instance so that the
// component does not need to (re-)specify the widget slug.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for? 🤔

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have refactored the implementation and updated the come

@mehidi258

Copy link
Copy Markdown
Collaborator Author

Most of my comments here apply to all of the components in question, so please have a look through and address the comments in both the files with comments and apply them to similar files as well. 🙂

Please be sure to answer any questions and not just implement changes. I'm curious about the thought process here when asking, so I'd like to understand the original decisions made here even if we do want to change something.

Hi @tofumatt, thanks for all the feedback here, It's really helpful.

Yes, this PR started from AI-generated code, and while I went through and fixed a lot, I clearly missed several things that I should have caught myself before asking for review. Appreciate you taking the time to point them out.
Now i have done huge refactoring to remove duplication as much as possible. Passing to you for re-verify.

@tofumatt tofumatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for your work on this PR; I know it was a big one and I think the changes you made are a lot better 👍🏻

@tofumatt
tofumatt merged commit f1f5298 into develop Sep 11, 2026
50 of 52 checks passed
@tofumatt
tofumatt deleted the enhancement/13404-site-goals-selling-products-key-metrics branch September 11, 2026 13:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add the Site Goals Key Metric tiles for the Selling products group

3 participants