Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create the "Top converting traffic source" key metric widget tile. #7253

Merged
merged 9 commits into from
Jul 10, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ export default {
decorators: [
( Story, { args } ) => {
const setupRegistry = ( registry ) => {
global._googlesitekitUserData.isUserInputCompleted = false;
provideModules( registry, [
{
slug: 'analytics-4',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ export default {
decorators: [
( Story, { args } ) => {
const setupRegistry = ( registry ) => {
global._googlesitekitUserData.isUserInputCompleted = false;
provideModules( registry, [
{
slug: 'analytics-4',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ export default {
decorators: [
( Story, { args } ) => {
const setupRegistry = ( registry ) => {
global._googlesitekitUserData.isUserInputCompleted = false;
provideModules( registry, [
{
slug: 'analytics-4',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,23 @@
*/
import PropTypes from 'prop-types';

/**
* WordPress dependencies
*/
import { __, sprintf } 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 { useInViewSelect } from '../../../../hooks/useInViewSelect';
import MetricTileText from '../../../../components/KeyMetrics/MetricTileText';
import { numFmt } from '../../../../util';

const { useSelect } = Data;

Expand All @@ -37,14 +49,92 @@ export default function TopConvertingTrafficSourceWidget( {
select( CORE_USER ).isKeyMetricsWidgetHidden()
Copy link
Collaborator

Choose a reason for hiding this comment

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

This must have been scaffolded before, but there is a utility used by other widget tiles to simplify this which also prevents side effects from the widget rendering without the need to have conditional selects

export default whenKeyMetricsWidgetVisible()( LoyalVisitorsWidget );

It might not be worth changing at this point as this should be covered by #7061

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks @aaemnnosttv, that's useful to know. It does seem this should conceptually be covered by #7061, but looking at the IB, it only specifies the removal of the whenKeyMetricsWidgetVisible HOC, with the only reference to keyMetricsWidgetHidden being in a struck-out line.

Noting that there are other widgets currently doing a similar check with isKeyMetricsWidgetHidden(), and furthermore that #7061 is currently in execution with @jimmymadon, I'd say it's probably not worth updating this widget, but rather getting it merged while Jimmy's still working on #7061, and dropping him a line to make sure he merges develop into his branch and updates all of these widgets to remove the checks based on isKeyMetricsWidgetHidden().

$ grep -l isKeyMetricsWidgetHidden assets/js/**/*Widget.js

assets/js/modules/analytics-4/components/widgets/EngagedTrafficSourceWidget.js
assets/js/modules/analytics-4/components/widgets/PopularProductsWidget.js
assets/js/modules/analytics-4/components/widgets/TopCitiesWidget.js
assets/js/modules/analytics-4/components/widgets/TopConvertingTrafficSourceWidget.js
assets/js/modules/analytics-4/components/widgets/TopCountriesWidget.js
assets/js/modules/analytics-4/components/widgets/TopTrafficSourceWidget.js

Copy link
Collaborator Author

@techanvil techanvil Jul 6, 2023

Choose a reason for hiding this comment

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

Ah - actually, I should have looked into #7061 a bit more deeply, I've now seen there's an open PR in which @jimmymadon is already addressing those isKeyMetricsWidgetHidden() checks in the other widgets. So this really just becomes a question about timing and if it makes sense to merge this PR while #7061 is in progress...

Copy link
Collaborator Author

@techanvil techanvil Jul 6, 2023

Choose a reason for hiding this comment

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

Update: Having spoken to Jimmy on Slack he's happy to take the approach of merging develop into his branch once this one's merged, and updating this component along with the others.

);

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

const reportOptions = {
...dates,
dimensions: [ 'sessionDefaultChannelGroup' ],
metrics: [
{
name: 'sessionConversionRate',
},
],
limit: 1,
orderBy: 'sessionConversionRate',
};

const report = useInViewSelect( ( select ) => {
if ( keyMetricsWidgetHidden !== false ) {
return null;
}

return select( MODULES_ANALYTICS_4 ).getReport( reportOptions );
} );

const loading = useSelect(
( select ) =>
! select( MODULES_ANALYTICS_4 ).hasFinishedResolution(
'getReport',
[ reportOptions ]
)
);

if ( keyMetricsWidgetHidden !== false ) {
return <WidgetNull />;
}

const getRowForDateRange = ( dateRange ) => {
if ( ! report?.rows ) {
return null;
}

// Filter the report to get only rows that match the given date range.
const rows = report.rows.filter(
( { dimensionValues: [ , dateValue ] } ) =>
dateValue.value === dateRange
);

// As the report is limited to 1 row per date range, return the first row.
return rows[ 0 ];
};

const currentRow = getRowForDateRange( 'date_range_0' );
const previousRow = getRowForDateRange( 'date_range_1' );

const topChannelGroup = currentRow?.dimensionValues?.[ 0 ].value || '-';
const topConversionRate = parseFloat(
currentRow?.metricValues?.[ 0 ].value || '0'
);
const previousTopConversionRate = parseFloat(
previousRow?.metricValues?.[ 0 ].value || '0'
);

const format = {
style: 'percent',
signDisplay: 'never',
maximumFractionDigits: 1,
};

return (
<Widget>
<div>TODO: UI for TopConvertingTrafficSourceWidget</div>
</Widget>
<MetricTileText
Widget={ Widget }
title={ __( 'Top converting traffic source', 'google-site-kit' ) }
metricValue={ topChannelGroup }
metricValueFormat={ format }
subText={ sprintf(
/* translators: %d: Percentage of visits that led to conversions. */
__( '%s of visits led to conversions', 'google-site-kit' ),
numFmt( topConversionRate, format )
) }
previousValue={ previousTopConversionRate }
currentValue={ topConversionRate }
loading={ loading }
/>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/**
* TopConvertingTrafficSourceWidget 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 {
provideKeyMetrics,
provideModules,
} from '../../../../../../tests/js/utils';
import { withWidgetComponentProps } from '../../../../googlesitekit/widgets/util';
import WithRegistrySetup from '../../../../../../tests/js/WithRegistrySetup';
import TopConvertingTrafficSourceWidget from './TopConvertingTrafficSourceWidget';
import { CORE_USER } from '../../../../googlesitekit/datastore/user/constants';
import { MODULES_ANALYTICS_4 } from '../../datastore/constants';
import {
getAnalytics4MockResponse,
provideAnalytics4MockReport,
} from '../../utils/data-mock';
import { replaceValuesInAnalytics4ReportWithZeroData } from '../../../../../../.storybook/utils/zeroReports';

const reportOptions = {
compareStartDate: '2020-07-14',
compareEndDate: '2020-08-10',
startDate: '2020-08-11',
endDate: '2020-09-07',
dimensions: [ 'sessionDefaultChannelGroup' ],
metrics: [
{
name: 'sessionConversionRate',
},
],
limit: 1,
orderBy: 'sessionConversionRate',
};

const WidgetWithComponentProps = withWidgetComponentProps(
'kmAnalyticsTopTrafficSource'
)( TopConvertingTrafficSourceWidget );

const Template = ( { setupRegistry, ...args } ) => (
<WithRegistrySetup func={ setupRegistry }>
<WidgetWithComponentProps { ...args } />
</WithRegistrySetup>
);

export const Ready = Template.bind( {} );
Ready.storyName = 'Ready';
Ready.args = {
setupRegistry: ( registry ) => {
provideAnalytics4MockReport( registry, reportOptions );
},
};
Ready.scenario = {
label: 'KeyMetrics/TopConvertingTrafficSourceWidget/Ready',
};

export const Loading = Template.bind( {} );
Loading.storyName = 'Loading';
Loading.args = {
setupRegistry: ( { dispatch } ) => {
dispatch( MODULES_ANALYTICS_4 ).startResolution( 'getReport', [
reportOptions,
] );
},
};
Loading.scenario = {
label: 'KeyMetrics/TopConvertingTrafficSourceWidget/Loading',
};
Loading.decorators = [
( Story ) => {
// Ensure the animation is paused for VRT tests to correctly capture the loading state.
return (
<div className="googlesitekit-vrt-animation-paused">
<Story />
</div>
);
},
];

export const ZeroData = Template.bind( {} );
ZeroData.storyName = 'Zero Data';
ZeroData.args = {
setupRegistry: ( { dispatch } ) => {
dispatch( MODULES_ANALYTICS_4 ).receiveGetReport(
replaceValuesInAnalytics4ReportWithZeroData(
getAnalytics4MockResponse( reportOptions )
),
{
options: reportOptions,
}
);
},
};
ZeroData.scenario = {
label: 'KeyMetrics/TopConvertingTrafficSourceWidget/ZeroData',
};

export default {
title: 'Key Metrics/TopConvertingTrafficSourceWidget',
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 (
<WithRegistrySetup func={ setupRegistry }>
<Story />
</WithRegistrySetup>
);
},
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* TopConvertingTrafficSourceWidget 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_CONVERTING_TRAFFIC_SOURCE,
} from '../../../../googlesitekit/datastore/user/constants';
import TopConvertingTrafficSourceWidget from './TopConvertingTrafficSourceWidget';

describe( 'TopConvertingTrafficSourceWidget', () => {
const { Widget, WidgetNull } = getWidgetComponentProps(
KM_ANALYTICS_TOP_CONVERTING_TRAFFIC_SOURCE
);

it.each( [ undefined, true ] )(
'renders null when isKeyMetricsWidgetHidden() returns %s',
async ( isWidgetHidden ) => {
const { container, waitForRegistry } = render(
<TopConvertingTrafficSourceWidget
Widget={ Widget }
WidgetNull={ WidgetNull }
/>,
{
setupRegistry: ( registry ) => {
provideKeyMetrics( registry, { isWidgetHidden } );
},
}
);
await waitForRegistry();

expect( container ).toBeEmptyDOMElement();
}
);

it( 'renders correctly with the expected metrics when the Key Metrics widget is not hidden', async () => {
const { container, waitForRegistry } = render(
<TopConvertingTrafficSourceWidget
Widget={ Widget }
WidgetNull={ WidgetNull }
/>,
{
setupRegistry: ( registry ) => {
registry
.dispatch( CORE_USER )
.setReferenceDate( '2020-09-08' );

provideKeyMetrics( registry );
provideAnalytics4MockReport( registry, {
compareStartDate: '2020-07-14',
compareEndDate: '2020-08-10',
startDate: '2020-08-11',
endDate: '2020-09-07',
dimensions: [ 'sessionDefaultChannelGroup' ],
metrics: [
{
name: 'sessionConversionRate',
},
],
limit: 1,
orderBy: 'sessionConversionRate',
} );
},
}
);
await waitForRegistry();

expect( container ).toMatchSnapshot();
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@ export default {
decorators: [
( Story, { args } ) => {
const setupRegistry = ( registry ) => {
global._googlesitekitUserData.isUserInputCompleted = false;
provideModules( registry, [
{
slug: 'analytics-4',
Expand Down
Loading