diff --git a/assets/js/components/KeyMetrics/key-metrics-widgets.js b/assets/js/components/KeyMetrics/key-metrics-widgets.js index e8979385766..84b00db640f 100644 --- a/assets/js/components/KeyMetrics/key-metrics-widgets.js +++ b/assets/js/components/KeyMetrics/key-metrics-widgets.js @@ -48,6 +48,7 @@ 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'; @@ -382,6 +383,18 @@ const KEY_METRICS_WIDGETS = { ], displayInList: shouldDisplayWidgetWithConversionEvent, }, + [ KM_ANALYTICS_TOP_CITIES_DRIVING_ADD_TO_CART ]: { + title: __( 'Top cities driving add to cart', 'google-site-kit' ), + description: __( + 'Cities where visitors most frequently add products to their carts', + 'google-site-kit' + ), + infoTooltip: __( + 'Cities where visitors most frequently add products to their carts', + 'google-site-kit' + ), + requiredConversionEventName: [ 'add_to_cart' ], + }, [ KM_ANALYTICS_TOP_CITIES_DRIVING_PURCHASES ]: { title: __( 'Top cities driving purchases', 'google-site-kit' ), description: __( diff --git a/assets/js/googlesitekit/datastore/user/constants.js b/assets/js/googlesitekit/datastore/user/constants.js index 3a84e8dd093..dc43049c460 100644 --- a/assets/js/googlesitekit/datastore/user/constants.js +++ b/assets/js/googlesitekit/datastore/user/constants.js @@ -62,6 +62,8 @@ export const KM_ANALYTICS_RETURNING_VISITORS = 'kmAnalyticsReturningVisitors'; export const KM_ANALYTICS_TOP_CITIES = 'kmAnalyticsTopCities'; export const KM_ANALYTICS_TOP_CITIES_DRIVING_LEADS = 'kmAnalyticsTopCitiesDrivingLeads'; +export const KM_ANALYTICS_TOP_CITIES_DRIVING_ADD_TO_CART = + 'kmAnalyticsTopCitiesDrivingAddToCart'; export const KM_ANALYTICS_TOP_CITIES_DRIVING_PURCHASES = 'kmAnalyticsTopCitiesDrivingPurchases'; export const KM_ANALYTICS_TOP_CONVERTING_TRAFFIC_SOURCE = @@ -92,6 +94,7 @@ export const keyMetricsGA4Widgets = [ KM_ANALYTICS_TOP_CATEGORIES, KM_ANALYTICS_TOP_CITIES, KM_ANALYTICS_TOP_CITIES_DRIVING_LEADS, + KM_ANALYTICS_TOP_CITIES_DRIVING_ADD_TO_CART, KM_ANALYTICS_TOP_CITIES_DRIVING_PURCHASES, KM_ANALYTICS_TOP_CONVERTING_TRAFFIC_SOURCE, KM_ANALYTICS_TOP_COUNTRIES, diff --git a/assets/js/modules/analytics-4/components/widgets/TopCitiesDrivingAddToCartWidget.js b/assets/js/modules/analytics-4/components/widgets/TopCitiesDrivingAddToCartWidget.js new file mode 100644 index 00000000000..b1ecc046c7b --- /dev/null +++ b/assets/js/modules/analytics-4/components/widgets/TopCitiesDrivingAddToCartWidget.js @@ -0,0 +1,145 @@ +/** + * TopCitiesDrivingAddToCartWidget 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_CITIES_DRIVING_ADD_TO_CART, +} from '../../../../googlesitekit/datastore/user/constants'; +import { + DATE_RANGE_OFFSET, + MODULES_ANALYTICS_4, +} from '../../datastore/constants'; +import { ZeroDataMessage } from '../common'; +import { numFmt } from '../../../../util'; +import { + MetricTileTable, + MetricTileTablePlainText, +} from '../../../../components/KeyMetrics'; +import whenActive from '../../../../util/when-active'; +import ConnectGA4CTATileWidget from './ConnectGA4CTATileWidget'; + +function TopCitiesDrivingAddToCartWidget( { Widget } ) { + const dates = useSelect( ( select ) => + select( CORE_USER ).getDateRangeDates( { + offsetDays: DATE_RANGE_OFFSET, + } ) + ); + + const detectedEvents = useSelect( ( select ) => + select( MODULES_ANALYTICS_4 ).getDetectedEvents() + ); + const hasRequiredEvent = detectedEvents?.includes( 'add_to_cart' ); + + const topCitiesReportOptions = { + ...dates, + dimensions: [ 'city' ], + dimensionFilters: { + city: { + filterType: 'stringFilter', + matchType: 'EXACT', + value: '(not set)', + notExpression: true, + }, + }, + metrics: [ { name: 'addToCarts' } ], + orderby: [ + { + metric: { + metricName: 'addToCarts', + }, + desc: true, + }, + ], + limit: 3, + }; + + const topCitiesReport = useInViewSelect( + ( select ) => + hasRequiredEvent + ? select( MODULES_ANALYTICS_4 ).getReport( + topCitiesReportOptions + ) + : undefined, + [ topCitiesReportOptions ] + ); + + const error = useSelect( ( select ) => + select( MODULES_ANALYTICS_4 ).getErrorForSelector( 'getReport', [ + topCitiesReportOptions, + ] ) + ); + + const loading = useSelect( ( select ) => + hasRequiredEvent + ? ! select( MODULES_ANALYTICS_4 ).hasFinishedResolution( + 'getReport', + [ topCitiesReportOptions ] + ) + : undefined + ); + + const { rows = [] } = topCitiesReport || {}; + + const columns = [ + { + field: 'dimensionValues', + Component( { fieldValue } ) { + const [ title ] = fieldValue; + + return ; + }, + }, + { + field: 'metricValues.0.value', + Component( { fieldValue } ) { + return { numFmt( fieldValue ) }; + }, + }, + ]; + + return ( + + ); +} + +TopCitiesDrivingAddToCartWidget.propTypes = { + Widget: PropTypes.elementType.isRequired, +}; + +export default whenActive( { + moduleName: 'analytics-4', + FallbackComponent: ConnectGA4CTATileWidget, +} )( TopCitiesDrivingAddToCartWidget ); diff --git a/assets/js/modules/analytics-4/components/widgets/TopCitiesDrivingAddToCartWidget.stories.js b/assets/js/modules/analytics-4/components/widgets/TopCitiesDrivingAddToCartWidget.stories.js new file mode 100644 index 00000000000..f926b241a58 --- /dev/null +++ b/assets/js/modules/analytics-4/components/widgets/TopCitiesDrivingAddToCartWidget.stories.js @@ -0,0 +1,220 @@ +/** + * TopCitiesDrivingAddToCartWidget component stories. + * + * 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. + */ + +/** + * Internal dependencies + */ +import { CORE_USER } from '../../../../googlesitekit/datastore/user/constants'; +import { MODULES_ANALYTICS_4 } from '../../datastore/constants'; +import { + provideKeyMetrics, + provideModuleRegistrations, + provideModules, +} from '../../../../../../tests/js/utils'; +import { withWidgetComponentProps } from '../../../../googlesitekit/widgets/util'; +import { getAnalytics4MockResponse } from '../../utils/data-mock'; +import { replaceValuesInAnalytics4ReportWithZeroData } from '../../../../../../.storybook/utils/zeroReports'; +import WithRegistrySetup from '../../../../../../tests/js/WithRegistrySetup'; +import TopCitiesDrivingAddToCartWidget from './TopCitiesDrivingAddToCartWidget'; +import { ERROR_REASON_INSUFFICIENT_PERMISSIONS } from '../../../../util/errors'; + +const reportOptions = { + startDate: '2020-08-11', + endDate: '2020-09-07', + dimensions: [ 'city' ], + dimensionFilters: { + city: { + filterType: 'stringFilter', + matchType: 'EXACT', + value: '(not set)', + notExpression: true, + }, + }, + metrics: [ { name: 'addToCarts' } ], + orderby: [ + { + metric: { + metricName: 'addToCarts', + }, + desc: true, + }, + ], + limit: 3, +}; + +const WidgetWithComponentProps = withWidgetComponentProps( + 'kmAnalyticsTopCitiesDrivingAddToCart' +)( TopCitiesDrivingAddToCartWidget ); + +function Template( { setupRegistry, ...args } ) { + return ( + + + + ); +} + +export const Ready = Template.bind( {} ); +Ready.storyName = 'Ready'; +Ready.args = { + setupRegistry: ( registry ) => { + const report = getAnalytics4MockResponse( reportOptions ); + + registry.dispatch( MODULES_ANALYTICS_4 ).receiveGetReport( report, { + options: reportOptions, + } ); + }, +}; +Ready.scenario = { + label: 'KeyMetrics/TopCitiesDrivingAddToCartWidget/Ready', +}; + +export const Loading = Template.bind( {} ); +Loading.storyName = 'Loading'; +Loading.args = { + setupRegistry: ( { dispatch } ) => { + dispatch( MODULES_ANALYTICS_4 ).startResolution( 'getReport', [ + reportOptions, + ] ); + }, +}; + +export const ZeroData = Template.bind( {} ); +ZeroData.storyName = 'Zero Data'; +ZeroData.args = { + setupRegistry: ( { dispatch } ) => { + const report = getAnalytics4MockResponse( reportOptions ); + const zeroReport = + replaceValuesInAnalytics4ReportWithZeroData( report ); + + dispatch( MODULES_ANALYTICS_4 ).receiveGetReport( zeroReport, { + options: reportOptions, + } ); + }, +}; +ZeroData.scenario = { + label: 'KeyMetrics/TopCitiesDrivingAddToCartWidget/ZeroData', +}; + +export const Error = Template.bind( {} ); +Error.storyName = 'Error'; +Error.args = { + setupRegistry: ( { dispatch } ) => { + const errorObject = { + code: 400, + message: 'Test error message. ', + data: { + status: 400, + reason: 'badRequest', + }, + }; + + dispatch( MODULES_ANALYTICS_4 ).receiveError( + errorObject, + 'getReport', + [ reportOptions ] + ); + + dispatch( MODULES_ANALYTICS_4 ).finishResolution( 'getReport', [ + reportOptions, + ] ); + }, +}; +Error.scenario = { + label: 'KeyMetrics/TopCitiesDrivingAddToCartWidget/Error', +}; + +export const InsufficientPermissions = Template.bind( {} ); +InsufficientPermissions.storyName = 'Insufficient Permissions'; +InsufficientPermissions.args = { + setupRegistry: ( { dispatch } ) => { + const errorObject = { + code: 403, + message: 'Test error message. ', + data: { + status: 403, + reason: ERROR_REASON_INSUFFICIENT_PERMISSIONS, + }, + }; + + dispatch( MODULES_ANALYTICS_4 ).receiveError( + errorObject, + 'getReport', + [ reportOptions ] + ); + + dispatch( MODULES_ANALYTICS_4 ).finishResolution( 'getReport', [ + reportOptions, + ] ); + }, +}; + +InsufficientPermissions.scenario = { + label: 'KeyMetrics/TopCitiesDrivingAddToCartWidget/InsufficientPermissions', +}; + +export default { + title: 'Key Metrics/TopCitiesDrivingAddToCartWidget', + decorators: [ + ( Story, { args } ) => { + const setupRegistry = ( registry ) => { + provideModules( registry, [ + { + slug: 'analytics-4', + active: true, + connected: true, + }, + ] ); + + provideModuleRegistrations( registry ); + + const [ accountID, propertyID, webDataStreamID ] = [ + '12345', + '34567', + '56789', + ]; + + registry + .dispatch( MODULES_ANALYTICS_4 ) + .setAccountID( accountID ); + registry + .dispatch( MODULES_ANALYTICS_4 ) + .setPropertyID( propertyID ); + registry + .dispatch( MODULES_ANALYTICS_4 ) + .setWebDataStreamID( webDataStreamID ); + registry + .dispatch( MODULES_ANALYTICS_4 ) + .setDetectedEvents( [ 'add_to_cart' ] ); + + registry.dispatch( CORE_USER ).setReferenceDate( '2020-09-08' ); + + provideKeyMetrics( registry ); + + // Call story-specific setup. + args.setupRegistry( registry ); + }; + + return ( + + + + ); + }, + ], +}; diff --git a/assets/js/modules/analytics-4/components/widgets/index.js b/assets/js/modules/analytics-4/components/widgets/index.js index e01e7e0dce0..a9b2ad1e98c 100644 --- a/assets/js/modules/analytics-4/components/widgets/index.js +++ b/assets/js/modules/analytics-4/components/widgets/index.js @@ -27,6 +27,7 @@ export { default as PopularProductsWidget } from './PopularProductsWidget'; export { default as ReturningVisitorsWidget } from './ReturningVisitorsWidget'; export { default as TopCitiesWidget } from './TopCitiesWidget'; export { default as TopCitiesDrivingLeadsWidget } from './TopCitiesDrivingLeadsWidget'; +export { default as TopCitiesDrivingAddToCartWidget } from './TopCitiesDrivingAddToCartWidget'; export { default as TopCitiesDrivingPurchasesWidget } from './TopCitiesDrivingPurchasesWidget'; export { default as TopCountriesWidget } from './TopCountriesWidget'; export { default as TopTrafficSourceWidget } from './TopTrafficSourceWidget'; diff --git a/assets/js/modules/analytics-4/index.js b/assets/js/modules/analytics-4/index.js index ea26c10fa63..cf83b92b59b 100644 --- a/assets/js/modules/analytics-4/index.js +++ b/assets/js/modules/analytics-4/index.js @@ -34,6 +34,7 @@ import { ReturningVisitorsWidget, TopCitiesWidget, TopCitiesDrivingLeadsWidget, + TopCitiesDrivingAddToCartWidget, TopCitiesDrivingPurchasesWidget, TopCountriesWidget, TopTrafficSourceWidget, @@ -78,6 +79,7 @@ import { KM_ANALYTICS_TOP_TRAFFIC_SOURCE, KM_ANALYTICS_VISIT_LENGTH, KM_ANALYTICS_VISITS_PER_VISITOR, + KM_ANALYTICS_TOP_CITIES_DRIVING_ADD_TO_CART, } from '../../googlesitekit/datastore/user/constants'; import { SettingsEdit, SettingsView } from './components/settings'; import { SetupMain } from './components/setup'; @@ -538,6 +540,22 @@ export const registerWidgets = ( widgets ) => { [ AREA_MAIN_DASHBOARD_KEY_METRICS_PRIMARY ] ); + widgets.registerWidget( + KM_ANALYTICS_TOP_CITIES_DRIVING_ADD_TO_CART, + { + Component: TopCitiesDrivingAddToCartWidget, + width: widgets.WIDGET_WIDTHS.QUARTER, + priority: 1, + wrapWidget: false, + modules: [ 'analytics-4' ], + isActive: ( select ) => + select( CORE_USER ).isKeyMetricActive( + KM_ANALYTICS_TOP_CITIES_DRIVING_ADD_TO_CART + ), + }, + [ AREA_MAIN_DASHBOARD_KEY_METRICS_PRIMARY ] + ); + widgets.registerWidget( KM_ANALYTICS_TOP_CITIES_DRIVING_PURCHASES, { diff --git a/assets/js/modules/analytics-4/utils/data-mock.js b/assets/js/modules/analytics-4/utils/data-mock.js index a92d1f99f42..03f47880cd5 100644 --- a/assets/js/modules/analytics-4/utils/data-mock.js +++ b/assets/js/modules/analytics-4/utils/data-mock.js @@ -57,6 +57,7 @@ const ANALYTICS_4_METRIC_TYPES = { sessionsPerUser: 'TYPE_FLOAT', totalAdRevenue: 'TYPE_INTEGER', eventCount: 'TYPE_INTEGER', + addToCarts: 'TYPE_INTEGER', ecommercePurchases: 'TYPE_INTEGER', }; diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Error_0_document_0_small.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Error_0_document_0_small.png new file mode 100644 index 00000000000..260d2d43671 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Error_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Error_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Error_0_document_1_medium.png new file mode 100644 index 00000000000..135d9e0f982 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Error_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Error_0_document_2_large.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Error_0_document_2_large.png new file mode 100644 index 00000000000..cd137530ae5 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Error_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_InsufficientPermissions_0_document_0_small.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_InsufficientPermissions_0_document_0_small.png new file mode 100644 index 00000000000..c813d0fedaa Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_InsufficientPermissions_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_InsufficientPermissions_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_InsufficientPermissions_0_document_1_medium.png new file mode 100644 index 00000000000..86a79f084e2 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_InsufficientPermissions_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_InsufficientPermissions_0_document_2_large.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_InsufficientPermissions_0_document_2_large.png new file mode 100644 index 00000000000..96ca4bea2e3 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_InsufficientPermissions_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Ready_0_document_0_small.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Ready_0_document_0_small.png new file mode 100644 index 00000000000..5339aa1a132 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Ready_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Ready_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Ready_0_document_1_medium.png new file mode 100644 index 00000000000..c798928a648 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Ready_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Ready_0_document_2_large.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Ready_0_document_2_large.png new file mode 100644 index 00000000000..612c7e88452 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_Ready_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_ZeroData_0_document_0_small.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_ZeroData_0_document_0_small.png new file mode 100644 index 00000000000..bed19c836ef Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_ZeroData_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_ZeroData_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_ZeroData_0_document_1_medium.png new file mode 100644 index 00000000000..4d5e33b5e7a Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_ZeroData_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_ZeroData_0_document_2_large.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_ZeroData_0_document_2_large.png new file mode 100644 index 00000000000..018f6e30e46 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesDrivingAddToCartWidget_ZeroData_0_document_2_large.png differ