diff --git a/assets/js/components/KeyMetrics/MetricTileTable.js b/assets/js/components/KeyMetrics/MetricTileTable.js index f10ca5b4058..ded66071212 100644 --- a/assets/js/components/KeyMetrics/MetricTileTable.js +++ b/assets/js/components/KeyMetrics/MetricTileTable.js @@ -34,7 +34,7 @@ export default function MetricTileTable( props ) { rows = [], columns = [], limit, - zeroState: ZeroState, + ZeroState, } = props; let tileBody = null; @@ -114,5 +114,5 @@ MetricTileTable.propTypes = { rows: PropTypes.array, columns: PropTypes.array, limit: PropTypes.number, - zeroState: PropTypes.func, + ZeroState: PropTypes.elementType, }; diff --git a/assets/js/components/KeyMetrics/MetricTileTable.stories.js b/assets/js/components/KeyMetrics/MetricTileTable.stories.js index 5fec00a96fe..af7119ad159 100644 --- a/assets/js/components/KeyMetrics/MetricTileTable.stories.js +++ b/assets/js/components/KeyMetrics/MetricTileTable.stories.js @@ -70,7 +70,7 @@ ZeroData.args = { title, rows: [], columns, - zeroState: () => 'No data available', + ZeroState: () =>
No data available
, }; ZeroData.scenario = { label: 'KeyMetrics/MetricTileTable/ZeroData', diff --git a/assets/js/components/KeyMetrics/MetricTileTablePlainText.js b/assets/js/components/KeyMetrics/MetricTileTablePlainText.js new file mode 100644 index 00000000000..6840301cd6d --- /dev/null +++ b/assets/js/components/KeyMetrics/MetricTileTablePlainText.js @@ -0,0 +1,34 @@ +/** + * MetricTileTablePlainText component. + * + * Site Kit by Google, Copyright 2023 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'; + +export default function MetricTileTablePlainText( { content } ) { + return ( +

+ { content } +

+ ); +} + +MetricTileTablePlainText.propTypes = { + content: PropTypes.string.isRequired, +}; diff --git a/assets/js/components/KeyMetrics/index.js b/assets/js/components/KeyMetrics/index.js index 6bf83cfce3a..99eba3a72f6 100644 --- a/assets/js/components/KeyMetrics/index.js +++ b/assets/js/components/KeyMetrics/index.js @@ -17,4 +17,5 @@ export { default as KeyMetricsSetupCTAWidget } from './KeyMetricsSetupCTAWidget'; export { default as MetricTileNumeric } from './MetricTileNumeric'; export { default as MetricTileTable } from './MetricTileTable'; +export { default as MetricTileTablePlainText } from './MetricTileTablePlainText'; export { default as MetricTileText } from './MetricTileText'; diff --git a/assets/js/modules/analytics-4/components/widgets/PopularContentWidget.js b/assets/js/modules/analytics-4/components/widgets/PopularContentWidget.js index e173c274860..497e59b141e 100644 --- a/assets/js/modules/analytics-4/components/widgets/PopularContentWidget.js +++ b/assets/js/modules/analytics-4/components/widgets/PopularContentWidget.js @@ -119,7 +119,7 @@ export default function PopularContentWidget( props ) { loading={ loading } rows={ rows } columns={ columns } - zeroState={ ZeroDataMessage } + ZeroState={ ZeroDataMessage } /> ); } diff --git a/assets/js/modules/analytics-4/components/widgets/TopCitiesWidget.js b/assets/js/modules/analytics-4/components/widgets/TopCitiesWidget.js index 7e5dd3c788a..7443c8ca0f6 100644 --- a/assets/js/modules/analytics-4/components/widgets/TopCitiesWidget.js +++ b/assets/js/modules/analytics-4/components/widgets/TopCitiesWidget.js @@ -21,15 +21,100 @@ */ import PropTypes from 'prop-types'; +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import Data from 'googlesitekit-data'; +import { CORE_USER } from '../../../../googlesitekit/datastore/user/constants'; +import { + DATE_RANGE_OFFSET, + MODULES_ANALYTICS_4, +} from '../../datastore/constants'; +import { ZeroDataMessage } from '../../../analytics/components/common'; +import { numFmt } from '../../../../util'; +import { + MetricTileTable, + MetricTileTablePlainText, +} from '../../../../components/KeyMetrics'; +const { useSelect, useInViewSelect } = Data; + export default function TopCitiesWidget( { Widget } ) { + const dates = useSelect( ( select ) => + select( CORE_USER ).getDateRangeDates( { + offsetDays: DATE_RANGE_OFFSET, + } ) + ); + + const topcCitiesReportOptions = { + ...dates, + dimensions: [ 'city' ], + metrics: [ { name: 'totalUsers' } ], + orderby: [ + { + metric: { + metricName: 'totalUsers', + }, + desc: true, + }, + ], + limit: 3, + }; + + const topCitiesReport = useInViewSelect( ( select ) => + select( MODULES_ANALYTICS_4 ).getReport( topcCitiesReportOptions ) + ); + + const loading = useSelect( + ( select ) => + ! select( MODULES_ANALYTICS_4 ).hasFinishedResolution( + 'getReport', + [ topcCitiesReportOptions ] + ) + ); + + const { rows = [], totals = [] } = topCitiesReport || {}; + + const totalUsers = totals?.[ 0 ]?.metricValues?.[ 0 ]?.value; + + const columns = [ + { + field: 'dimensionValues', + Component: ( { fieldValue } ) => { + const [ title ] = fieldValue; + + return ; + }, + }, + { + field: 'metricValues.0.value', + Component: ( { fieldValue } ) => ( + + { numFmt( fieldValue / totalUsers, { + style: 'percent', + maximumFractionDigits: 1, + } ) } + + ), + }, + ]; + return ( - -
TODO: UI for TopCitiesWidget
-
+ ); } TopCitiesWidget.propTypes = { Widget: PropTypes.elementType.isRequired, - WidgetNull: PropTypes.elementType.isRequired, }; diff --git a/assets/js/modules/analytics-4/components/widgets/TopCitiesWidget.stories.js b/assets/js/modules/analytics-4/components/widgets/TopCitiesWidget.stories.js new file mode 100644 index 00000000000..4057ae63a0d --- /dev/null +++ b/assets/js/modules/analytics-4/components/widgets/TopCitiesWidget.stories.js @@ -0,0 +1,143 @@ +/** + * TopCitiesWidget component stories. + * + * Site Kit by Google, Copyright 2023 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, + 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 TopCitiesWidget from './TopCitiesWidget'; + +const reportOptions = { + startDate: '2020-08-11', + endDate: '2020-09-07', + dimensions: [ 'city' ], + metrics: [ { name: 'totalUsers' } ], + orderby: [ + { + metric: { + metricName: 'totalUsers', + }, + desc: true, + }, + ], + limit: 3, +}; + +const WidgetWithComponentProps = + withWidgetComponentProps( 'test' )( TopCitiesWidget ); + +const Template = ( { setupRegistry, ...args } ) => ( + + + +); + +export const Ready = Template.bind( {} ); +Ready.storyName = 'Ready'; +Ready.args = { + setupRegistry: ( registry ) => { + const report = getAnalytics4MockResponse( reportOptions ); + // Calculate sum of metricValues for all rows + const rowsSum = report.rows.reduce( ( total, row ) => { + return total + Number( row.metricValues[ 0 ].value ); + }, 0 ); + + // Generate totalValueForAllCities that is higher than the sum + const totalValueForAllCities = rowsSum * 2; + + // Adjust totals field in the mock response + report.totals = [ + { + dimensionValues: [ { value: 'RESERVED_TOTAL' } ], + metricValues: [ { value: totalValueForAllCities.toString() } ], + }, + ]; + registry.dispatch( MODULES_ANALYTICS_4 ).receiveGetReport( report, { + options: reportOptions, + } ); + }, +}; +Ready.scenario = { + label: 'KeyMetrics/TopCitiesWidget/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/TopCitiesWidget/ZeroData', +}; + +export default { + title: 'Key Metrics/TopCitiesWidget', + decorators: [ + ( Story, { args } ) => { + const setupRegistry = ( registry ) => { + provideModules( registry, [ + { + slug: 'analytics-4', + active: true, + connected: true, + }, + ] ); + + 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/TopCitiesWidget.test.js b/assets/js/modules/analytics-4/components/widgets/TopCitiesWidget.test.js new file mode 100644 index 00000000000..ae4054ed7a9 --- /dev/null +++ b/assets/js/modules/analytics-4/components/widgets/TopCitiesWidget.test.js @@ -0,0 +1,67 @@ +/** + * TopCitiesWidget component tests. + * + * Site Kit by Google, Copyright 2023 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 { render } from '../../../../../../tests/js/test-utils'; +import { provideKeyMetrics } from '../../../../../../tests/js/utils'; +import { provideAnalytics4MockReport } from '../../utils/data-mock'; +import { getWidgetComponentProps } from '../../../../googlesitekit/widgets/util'; +import { + CORE_USER, + KM_ANALYTICS_TOP_CITIES, +} from '../../../../googlesitekit/datastore/user/constants'; +import TopCitiesWidget from './TopCitiesWidget'; + +describe( 'TopCitiesWidget', () => { + const { Widget } = getWidgetComponentProps( KM_ANALYTICS_TOP_CITIES ); + + it( 'renders correctly with the expected metrics', async () => { + const { container, waitForRegistry } = render( + , + { + setupRegistry: ( registry ) => { + registry + .dispatch( CORE_USER ) + .setReferenceDate( '2020-09-08' ); + + provideKeyMetrics( registry ); + provideAnalytics4MockReport( registry, { + startDate: '2020-08-11', + endDate: '2020-09-07', + dimensions: [ 'city' ], + metrics: [ { name: 'totalUsers' } ], + orderby: [ + { + metric: { + metricName: 'totalUsers', + }, + desc: true, + }, + ], + limit: 3, + } ); + }, + } + ); + await waitForRegistry(); + + expect( container ).toMatchSnapshot(); + } ); +} ); diff --git a/assets/js/modules/analytics-4/components/widgets/__snapshots__/TopCitiesWidget.test.js.snap b/assets/js/modules/analytics-4/components/widgets/__snapshots__/TopCitiesWidget.test.js.snap new file mode 100644 index 00000000000..4389079dfac --- /dev/null +++ b/assets/js/modules/analytics-4/components/widgets/__snapshots__/TopCitiesWidget.test.js.snap @@ -0,0 +1,91 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TopCitiesWidget renders correctly with the expected metrics 1`] = ` +
+
+
+
+

+ Top cities driving traffic +

+
+
+
+
+

+ Dublin +

+
+
+ + 1,833.3% + +
+
+
+
+

+ (not set) +

+
+
+ + 466.7% + +
+
+
+
+

+ Cork +

+
+
+ + 100% + +
+
+
+
+
+
+
+
+`; diff --git a/assets/js/modules/analytics-4/utils/data-mock.js b/assets/js/modules/analytics-4/utils/data-mock.js index 90cafb6313e..e848b30b5c4 100644 --- a/assets/js/modules/analytics-4/utils/data-mock.js +++ b/assets/js/modules/analytics-4/utils/data-mock.js @@ -73,6 +73,15 @@ const ANALYTICS_4_DIMENSION_OPTIONS = { 'Italy', 'Mexico', ], + city: [ + 'Dublin', + '(not set)', + 'Cork', + 'New York', + 'London', + 'Los Angeles', + 'San Francisco', + ], deviceCategory: [ 'Desktop', 'Tablet', 'Mobile' ], pageTitle: ( i ) => ( i <= 12 ? `Test Post ${ i }` : false ), pagePath: ( i ) => ( i <= 12 ? `/test-post-${ i }/` : false ), diff --git a/assets/js/modules/search-console/components/widgets/PopularKeywordsWidget.js b/assets/js/modules/search-console/components/widgets/PopularKeywordsWidget.js index ecb7a86e67a..6926903bc6c 100644 --- a/assets/js/modules/search-console/components/widgets/PopularKeywordsWidget.js +++ b/assets/js/modules/search-console/components/widgets/PopularKeywordsWidget.js @@ -126,7 +126,7 @@ export default function PopularKeywordsWidget( { Widget } ) { loading={ loading } rows={ rows } columns={ columns } - zeroState={ ZeroDataMessage } + ZeroState={ ZeroDataMessage } limit={ 3 } /> ); diff --git a/assets/sass/components/key-metrics/_googlesitekit-km-widget-tile.scss b/assets/sass/components/key-metrics/_googlesitekit-km-widget-tile.scss index 2bd22239ef2..cb98b9f0db2 100644 --- a/assets/sass/components/key-metrics/_googlesitekit-km-widget-tile.scss +++ b/assets/sass/components/key-metrics/_googlesitekit-km-widget-tile.scss @@ -113,5 +113,13 @@ font-weight: $fw-medium; } } + + .googlesitekit-km-widget-tile__table-plain-text { + color: $c-surfaces-on-surface; + font-size: $fs-body-sm; + letter-spacing: $ls-xs; + line-height: $lh-body-sm; + margin: 0; + } } } diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_Ready_0_document_0_small.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_Ready_0_document_0_small.png new file mode 100644 index 00000000000..7d88ce7727c Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_Ready_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_Ready_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_Ready_0_document_1_medium.png new file mode 100644 index 00000000000..9221883c562 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_Ready_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_Ready_0_document_2_large.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_Ready_0_document_2_large.png new file mode 100644 index 00000000000..6440c34a0ad Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_Ready_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_ZeroData_0_document_0_small.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_ZeroData_0_document_0_small.png new file mode 100644 index 00000000000..a632e52bbb8 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_ZeroData_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_ZeroData_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_ZeroData_0_document_1_medium.png new file mode 100644 index 00000000000..bcebd4f6aad Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_ZeroData_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_ZeroData_0_document_2_large.png b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_ZeroData_0_document_2_large.png new file mode 100644 index 00000000000..065050dfad2 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_KeyMetrics_TopCitiesWidget_ZeroData_0_document_2_large.png differ