Skip to content

Commit

Permalink
Merge pull request #9420 from google/top-pages-kmw-9153
Browse files Browse the repository at this point in the history
Add Top Pages KMW.
  • Loading branch information
tofumatt authored Oct 2, 2024
2 parents 198cedb + 0f7183b commit 636a534
Show file tree
Hide file tree
Showing 13 changed files with 539 additions and 35 deletions.
50 changes: 16 additions & 34 deletions assets/js/components/KeyMetrics/key-metrics-widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ import {
KM_ANALYTICS_POPULAR_CONTENT,
KM_ANALYTICS_POPULAR_PRODUCTS,
KM_ANALYTICS_TOP_CITIES,
KM_ANALYTICS_TOP_CITIES_DRIVING_ADD_TO_CART,
KM_ANALYTICS_TOP_CITIES_DRIVING_LEADS,
KM_ANALYTICS_TOP_CITIES_DRIVING_PURCHASES,
KM_ANALYTICS_TOP_COUNTRIES,
KM_ANALYTICS_TOP_CONVERTING_TRAFFIC_SOURCE,
KM_ANALYTICS_TOP_PAGES_DRIVING_LEADS,
KM_ANALYTICS_PAGES_PER_VISIT,
KM_ANALYTICS_TOP_RETURNING_VISITOR_PAGES,
KM_SEARCH_CONSOLE_POPULAR_KEYWORDS,
Expand All @@ -51,12 +53,11 @@ import {
KM_ANALYTICS_TOP_CATEGORIES,
KM_ANALYTICS_POPULAR_AUTHORS,
KM_ANALYTICS_ADSENSE_TOP_EARNING_CONTENT,
KM_ANALYTICS_TOP_CITIES_DRIVING_ADD_TO_CART,
} from '../../googlesitekit/datastore/user/constants';
import { CORE_SITE } from '../../googlesitekit/datastore/site/constants';
import { MODULES_ANALYTICS_4 } from '../../modules/analytics-4/datastore/constants';
import { CORE_MODULES } from '../../googlesitekit/modules/datastore/constants';
import { isFeatureEnabled } from '../../features';
import { shouldDisplayWidgetWithConversionEvent } from './shouldDisplayWidgetWithConversionEvent';

/**
* Determines whether to show a widget the requires Analytics 4 and AdSense to be linked.
Expand Down Expand Up @@ -129,38 +130,6 @@ function shouldDisplayWidgetWithCustomDimensions(
);
}

/**
* Determines whether to display a widget that requires conversion reporting events
* in the key metrics selection panel.
*
* This function is attached to the widget object that requires the conversion reporting events and
* has the `requiredConversionEventName` property.
*
* @since 1.136.0
*
* @param {Function} select Data store select function.
* @param {boolean} isViewOnlyDashboard Whether the current dashboard is view only.
* @param {string} slug Key metric widget slug.
* @return {boolean} Whether to display the widget.
*/
function shouldDisplayWidgetWithConversionEvent(
select,
isViewOnlyDashboard,
slug
) {
if ( ! isFeatureEnabled( 'conversionReporting' ) ) {
return false;
}

return (
select( MODULES_ANALYTICS_4 ).hasConversionReportingEvents(
// This property is available to the widget object that requires the
// conversion reporting events, where the function is attached.
this.requiredConversionEventName
) || select( CORE_USER ).isKeyMetricActive( slug )
);
}

const KEY_METRICS_WIDGETS = {
[ KM_ANALYTICS_ADSENSE_TOP_EARNING_CONTENT ]: {
title: __( 'Top earning pages', 'google-site-kit' ),
Expand Down Expand Up @@ -479,6 +448,19 @@ const KEY_METRICS_WIDGETS = {
'google-site-kit'
),
},
[ KM_ANALYTICS_TOP_PAGES_DRIVING_LEADS ]: {
title: __( 'Top pages driving leads', 'google-site-kit' ),
description: __(
'Pages on which forms are most frequently submitted',
'google-site-kit'
),
requiredConversionEventName: [
'submit_lead_form',
'contact',
'generate_lead',
],
displayInList: shouldDisplayWidgetWithConversionEvent,
},
};

export { KEY_METRICS_WIDGETS };
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { isFeatureEnabled } from '../../features';
import { CORE_USER } from '../../googlesitekit/datastore/user/constants';
import { MODULES_ANALYTICS_4 } from '../../modules/analytics-4/datastore/constants';

/**
* Determines whether to display a widget that requires conversion reporting events
* in the key metrics selection panel.
*
* This function is attached to the widget object that requires the conversion reporting events and
* has the `requiredConversionEventName` property.
*
* @since 1.136.0
* @since n.e.x.t Moved function to its own file.
*
* @param {Function} select Data store select function.
* @param {boolean} isViewOnlyDashboard Whether the current dashboard is view only.
* @param {string} slug Key metric widget slug.
* @return {boolean} Whether to display the widget.
*/
export function shouldDisplayWidgetWithConversionEvent(
select,
isViewOnlyDashboard,
slug
) {
if ( ! isFeatureEnabled( 'conversionReporting' ) ) {
return false;
}

return (
select( MODULES_ANALYTICS_4 ).hasConversionReportingEvents(
// This property is available to the widget object that requires the
// conversion reporting events, where the function is attached.
this.requiredConversionEventName
) || select( CORE_USER ).isKeyMetricActive( slug )
);
}
2 changes: 2 additions & 0 deletions assets/js/googlesitekit/datastore/user/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ export const KM_ANALYTICS_TOP_CITIES_DRIVING_PURCHASES =
export const KM_ANALYTICS_TOP_CONVERTING_TRAFFIC_SOURCE =
'kmAnalyticsTopConvertingTrafficSource';
export const KM_ANALYTICS_TOP_COUNTRIES = 'kmAnalyticsTopCountries';
export const KM_ANALYTICS_TOP_PAGES_DRIVING_LEADS =
'kmAnalyticsTopPagesDrivingLeads';
export const KM_ANALYTICS_TOP_RECENT_TRENDING_PAGES =
'kmAnalyticsTopRecentTrendingPages';
export const KM_ANALYTICS_TOP_TRAFFIC_SOURCE = 'kmAnalyticsTopTrafficSource';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/**
* TopPagesDrivingLeadsWidget component.
*
* Site Kit by Google, Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* External dependencies
*/
import PropTypes from 'prop-types';

/**
* Internal dependencies
*/
import { useSelect, useInViewSelect } from 'googlesitekit-data';
import {
CORE_USER,
KM_ANALYTICS_TOP_PAGES_DRIVING_LEADS,
} from '../../../../googlesitekit/datastore/user/constants';
import {
DATE_RANGE_OFFSET,
MODULES_ANALYTICS_4,
} from '../../datastore/constants';
import {
MetricTileTable,
MetricTileTablePlainText,
} from '../../../../components/KeyMetrics';
import Link from '../../../../components/Link';
import { ZeroDataMessage } from '../common';
import { numFmt } from '../../../../util';
import whenActive from '../../../../util/when-active';
import ConnectGA4CTATileWidget from './ConnectGA4CTATileWidget';
import useViewOnly from '../../../../hooks/useViewOnly';

function TopPagesDrivingLeadsWidget( props ) {
const { Widget } = props;

const viewOnlyDashboard = useViewOnly();

const dates = useSelect( ( select ) =>
select( CORE_USER ).getDateRangeDates( {
offsetDays: DATE_RANGE_OFFSET,
} )
);

const detectedEvents = useSelect( ( select ) =>
select( MODULES_ANALYTICS_4 ).getDetectedEvents()
);
const eventNames = [
'submit_lead_form',
'contact',
'generate_lead',
].filter( ( item ) => detectedEvents?.includes( item ) );

if (
eventNames.includes( 'submit_lead_form' ) &&
eventNames.includes( 'contact' )
) {
eventNames.splice( eventNames.indexOf( 'contact' ), 1 );
}

const reportOptions = {
...dates,
dimensions: [ 'pagePath', 'eventName' ],
dimensionFilters: {
eventName: {
filterType: 'inListFilter',
value: eventNames,
},
},
metrics: [ { name: 'eventCount' } ],
orderby: [
{
metric: { metricName: 'eventCount' },
desc: true,
},
],
limit: 3,
};

const report = useInViewSelect(
( select ) =>
eventNames?.length
? select( MODULES_ANALYTICS_4 ).getReport( reportOptions )
: undefined,
[ eventNames, reportOptions ]
);

const error = useSelect( ( select ) =>
select( MODULES_ANALYTICS_4 ).getErrorForSelector( 'getReport', [
reportOptions,
] )
);

const titles = useInViewSelect(
( select ) => {
if ( ! eventNames?.length || error ) {
return undefined;
}

return select( MODULES_ANALYTICS_4 ).getPageTitles(
report,
reportOptions
);
},
[ eventNames, error, report, reportOptions ]
);

const loading = useSelect( ( select ) => {
if ( ! eventNames?.length ) {
return undefined;
}

return (
! select( MODULES_ANALYTICS_4 ).hasFinishedResolution(
'getReport',
[ reportOptions ]
) || titles === undefined
);
} );

const { rows = [] } = report || {};

const columns = [
{
field: 'dimensionValues.0.value',
Component( { fieldValue } ) {
const url = fieldValue;
const title = titles[ url ];
// Utilizing `useSelect` inside the component rather than
// returning its direct value to the `columns` array.
// This pattern ensures that the component re-renders correctly based on changes in state,
// preventing potential issues with stale or out-of-sync data.
// Note: This pattern is replicated in a few other spots within our codebase.
const serviceURL = useSelect( ( select ) => {
return ! viewOnlyDashboard
? select( MODULES_ANALYTICS_4 ).getServiceReportURL(
'all-pages-and-screens',
{
filters: {
unifiedPagePathScreen: url,
},
dates,
}
)
: null;
} );

if ( viewOnlyDashboard ) {
return <MetricTileTablePlainText content={ title } />;
}

return (
<Link
href={ serviceURL }
title={ title }
external
hideExternalIndicator
>
{ title }
</Link>
);
},
},
{
field: 'metricValues.0.value',
Component( { fieldValue } ) {
return <strong>{ numFmt( fieldValue ) }</strong>;
},
},
];

return (
<MetricTileTable
Widget={ Widget }
widgetSlug={ KM_ANALYTICS_TOP_PAGES_DRIVING_LEADS }
loading={ loading }
rows={ rows }
columns={ columns }
ZeroState={ ZeroDataMessage }
error={ error }
moduleSlug="analytics-4"
/>
);
}

TopPagesDrivingLeadsWidget.propTypes = {
Widget: PropTypes.elementType.isRequired,
};

export default whenActive( {
moduleName: 'analytics-4',
FallbackComponent: ConnectGA4CTATileWidget,
} )( TopPagesDrivingLeadsWidget );
Loading

0 comments on commit 636a534

Please sign in to comment.