From ac4c0b64627842e32e065e69315a0f7b8744ed6b Mon Sep 17 00:00:00 2001 From: Miguel Lezama Date: Sun, 22 Dec 2024 21:51:49 -0300 Subject: [PATCH 01/10] add more digits and tooltip --- .../stats/stats-email-summary/index.jsx | 87 ++++++++++++++++++- client/state/stats/lists/utils.js | 18 +++- .../horizontal-bar-grid-item.tsx | 3 +- .../src/horizontal-bar-list/types.ts | 5 +- 4 files changed, 105 insertions(+), 8 deletions(-) diff --git a/client/my-sites/stats/stats-email-summary/index.jsx b/client/my-sites/stats/stats-email-summary/index.jsx index 4e0550cfba3e9..d1fdcd27422e5 100644 --- a/client/my-sites/stats/stats-email-summary/index.jsx +++ b/client/my-sites/stats/stats-email-summary/index.jsx @@ -1,4 +1,6 @@ +import { Tooltip } from '@automattic/components'; import { localize } from 'i18n-calypso'; +import { useRef, useState } from 'react'; import { connect } from 'react-redux'; import titlecase from 'to-title-case'; import JetpackColophon from 'calypso/components/jetpack-colophon'; @@ -13,6 +15,30 @@ import '../stats-module/summary-nav.scss'; const StatsStrings = statsStringsFactory(); +const TooltipWrapper = ( { value, item, renderContent } ) => { + const triggerRef = useRef( null ); + const [ showTooltip, setShowTooltip ] = useState( false ); + + const formattedValue = + typeof value === 'number' ? Number( value ).toFixed( 2 ) : value.replace( '%', '' ).trim(); + + return ( + + setShowTooltip( true ) } + onMouseLeave={ () => setShowTooltip( false ) } + > + { `${ formattedValue }%` } + + + { renderContent( item ) } + + + ); +}; + const StatsEmailSummary = ( { translate, period, siteSlug } ) => { // Navigation settings. One of the following, depending on the summary view. // Traffic => /stats/day/ @@ -37,6 +63,46 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { } const navigationItems = [ { label: backLabel, href: backLink }, { label: title } ]; + const renderTooltipContent = ( item ) => { + const opensUnique = parseInt( item.opens_unique, 10 ) || 0; + const clicksUnique = parseInt( item.clicks_unique, 10 ) || 0; + const opens = parseInt( item.opens, 10 ) || 0; + const clicks = parseInt( item.clicks, 10 ) || 0; + const opensRate = parseFloat( item.opens_rate ) || 0; + const clicksRate = parseFloat( item.clicks_rate ) || 0; + + return ( +
+
{ translate( 'Total Opens: %(opens)d', { args: { opens } } ) }
+
+ { translate( 'Unique Opens: %(uniqueOpens)d', { + args: { uniqueOpens: opensUnique }, + } ) } +
+
+ { translate( 'Open Rate: %(openRate).2f%%', { + args: { openRate: opensRate }, + } ) } +
+
+ { translate( 'Total Clicks: %(clicks)d', { + args: { clicks }, + } ) } +
+
+ { translate( 'Unique Clicks: %(uniqueClicks)d', { + args: { uniqueClicks: clicksUnique }, + } ) } +
+
+ { translate( 'Click Rate: %(clickRate).2f%%', { + args: { clickRate: clicksRate }, + } ) } +
+
+ ); + }; + return (
{ ), body: ( item ) => ( - <> - { `${ item.opens_rate }%` } - + ), } } path="emails" @@ -78,7 +146,18 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { hideSummaryLink metricLabel={ translate( 'Clicks' ) } valueField="clicks_rate" - formatValue={ ( value ) => `${ value }%` } + formatValue={ ( value, item ) => { + if ( item?.opens !== undefined ) { + return ( + + ); + } + return { `${ value }%` }; + } } listItemClassName="stats__summary--narrow-mobile" /> diff --git a/client/state/stats/lists/utils.js b/client/state/stats/lists/utils.js index 10e4c9ab87a93..30e54ba733f97 100644 --- a/client/state/stats/lists/utils.js +++ b/client/state/stats/lists/utils.js @@ -974,7 +974,19 @@ export const normalizers = { const emailsData = get( data, [ 'posts' ], [] ); return emailsData.map( - ( { id, href, date, title, type, opens, clicks, opens_rate, clicks_rate } ) => { + ( { + id, + href, + date, + title, + type, + opens, + clicks, + opens_rate, + clicks_rate, + opens_unique, + clicks_unique, + } ) => { const detailPage = site ? `/stats/email/opens/day/${ id }/${ site.slug }` : null; return { id, @@ -982,11 +994,13 @@ export const normalizers = { date, label: title, type, - value: clicks || '0', + value: clicks_rate || '0', opens: opens || '0', clicks: clicks || '0', opens_rate: opens_rate || '0', clicks_rate: clicks_rate || '0', + opens_unique: opens_unique || '0', + clicks_unique: clicks_unique || '0', page: detailPage, actions: [ { diff --git a/packages/components/src/horizontal-bar-list/horizontal-bar-grid-item.tsx b/packages/components/src/horizontal-bar-list/horizontal-bar-grid-item.tsx index 6293b09b0449d..60a7a19d185d3 100644 --- a/packages/components/src/horizontal-bar-list/horizontal-bar-grid-item.tsx +++ b/packages/components/src/horizontal-bar-list/horizontal-bar-grid-item.tsx @@ -109,7 +109,7 @@ const HorizontalBarListItem = ( { return ; } if ( formatValue ) { - return formatValue( value ); + return formatValue( value, data ); } return usePlainCard ? value : numberFormat( value, 0 ); }; @@ -193,6 +193,7 @@ const HorizontalBarListItem = ( { isStatic={ isStatic } usePlainCard={ usePlainCard } isLinkUnderlined={ isLinkUnderlined } + formatValue={ formatValue } /> ); } ) } diff --git a/packages/components/src/horizontal-bar-list/types.ts b/packages/components/src/horizontal-bar-list/types.ts index 12afb623ab155..87509523cf68d 100644 --- a/packages/components/src/horizontal-bar-list/types.ts +++ b/packages/components/src/horizontal-bar-list/types.ts @@ -32,7 +32,10 @@ export type HorizontalBarListItemProps = { * @property {boolean} hasNoBackground - don't render the background bar and adjust indentation */ hasNoBackground?: boolean; - formatValue?: ( value: number ) => string; + /** + * @property {Function} formatValue - function to format the value display. Can optionally receive the full item data. + */ + formatValue?: ( value: number, item?: StatDataObject ) => React.ReactNode; }; type StatDataObject = { From d813b505445ebdc282877888c4129dfdbc3db9a1 Mon Sep 17 00:00:00 2001 From: Miguel Lezama Date: Sun, 22 Dec 2024 22:09:17 -0300 Subject: [PATCH 02/10] add total sends and improve tooltip --- .../stats/stats-email-summary/index.jsx | 48 +++++++++---------- client/state/stats/lists/utils.js | 2 + 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/client/my-sites/stats/stats-email-summary/index.jsx b/client/my-sites/stats/stats-email-summary/index.jsx index d1fdcd27422e5..e9f87d43400ab 100644 --- a/client/my-sites/stats/stats-email-summary/index.jsx +++ b/client/my-sites/stats/stats-email-summary/index.jsx @@ -64,40 +64,38 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { const navigationItems = [ { label: backLabel, href: backLink }, { label: title } ]; const renderTooltipContent = ( item ) => { - const opensUnique = parseInt( item.opens_unique, 10 ) || 0; - const clicksUnique = parseInt( item.clicks_unique, 10 ) || 0; - const opens = parseInt( item.opens, 10 ) || 0; - const clicks = parseInt( item.clicks, 10 ) || 0; - const opensRate = parseFloat( item.opens_rate ) || 0; - const clicksRate = parseFloat( item.clicks_rate ) || 0; + const opensUnique = parseInt( item.opens_unique, 10 ); + const clicksUnique = parseInt( item.clicks_unique, 10 ); + const opens = parseInt( item.opens, 10 ); + const clicks = parseInt( item.clicks, 10 ); + const opensRate = parseFloat( item.opens_rate ); + const clicksRate = parseFloat( item.clicks_rate ); + const totalSends = parseInt( item.total_sends, 10 ); return (
-
{ translate( 'Total Opens: %(opens)d', { args: { opens } } ) }
- { translate( 'Unique Opens: %(uniqueOpens)d', { - args: { uniqueOpens: opensUnique }, + { translate( 'Subscribers reached: %(sends)d', { + args: { sends: totalSends }, } ) }
- { translate( 'Open Rate: %(openRate).2f%%', { - args: { openRate: opensRate }, - } ) } -
-
- { translate( 'Total Clicks: %(clicks)d', { - args: { clicks }, - } ) } + { opensUnique + ? translate( 'Unique opens: %(uniqueOpens)d (%(openRate).2f%%)', { + args: { uniqueOpens: opensUnique, openRate: opensRate }, + } ) + : translate( 'Opens: %(opens)d)', { + args: { opens }, + } ) }
- { translate( 'Unique Clicks: %(uniqueClicks)d', { - args: { uniqueClicks: clicksUnique }, - } ) } -
-
- { translate( 'Click Rate: %(clickRate).2f%%', { - args: { clickRate: clicksRate }, - } ) } + { clicksUnique + ? translate( 'Unique clicks: %(uniqueClicks)d (%(clickRate).2f%%)', { + args: { uniqueClicks: clicksUnique, clickRate: clicksRate }, + } ) + : translate( 'Clicks: %(clicks)d ', { + args: { clicks }, + } ) }
); diff --git a/client/state/stats/lists/utils.js b/client/state/stats/lists/utils.js index 30e54ba733f97..ea9438c6828f5 100644 --- a/client/state/stats/lists/utils.js +++ b/client/state/stats/lists/utils.js @@ -986,6 +986,7 @@ export const normalizers = { clicks_rate, opens_unique, clicks_unique, + total_sends, } ) => { const detailPage = site ? `/stats/email/opens/day/${ id }/${ site.slug }` : null; return { @@ -1001,6 +1002,7 @@ export const normalizers = { clicks_rate: clicks_rate || '0', opens_unique: opens_unique || '0', clicks_unique: clicks_unique || '0', + total_sends: total_sends || '0', page: detailPage, actions: [ { From d39032c5324536b699e6e35ce5c670bd0ac12b14 Mon Sep 17 00:00:00 2001 From: Miguel Lezama Date: Tue, 24 Dec 2024 15:29:12 -0300 Subject: [PATCH 03/10] unique first --- client/my-sites/stats/stats-email-summary/index.jsx | 12 ++++++------ client/state/stats/lists/utils.js | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client/my-sites/stats/stats-email-summary/index.jsx b/client/my-sites/stats/stats-email-summary/index.jsx index e9f87d43400ab..f6b12f59eb905 100644 --- a/client/my-sites/stats/stats-email-summary/index.jsx +++ b/client/my-sites/stats/stats-email-summary/index.jsx @@ -64,8 +64,8 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { const navigationItems = [ { label: backLabel, href: backLink }, { label: title } ]; const renderTooltipContent = ( item ) => { - const opensUnique = parseInt( item.opens_unique, 10 ); - const clicksUnique = parseInt( item.clicks_unique, 10 ); + const uniqueOpens = parseInt( item.unique_opens, 10 ); + const uniqueClicks = parseInt( item.unique_clicks, 10 ); const opens = parseInt( item.opens, 10 ); const clicks = parseInt( item.clicks, 10 ); const opensRate = parseFloat( item.opens_rate ); @@ -80,18 +80,18 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { } ) }
- { opensUnique + { uniqueOpens ? translate( 'Unique opens: %(uniqueOpens)d (%(openRate).2f%%)', { - args: { uniqueOpens: opensUnique, openRate: opensRate }, + args: { uniqueOpens: uniqueOpens, openRate: opensRate }, } ) : translate( 'Opens: %(opens)d)', { args: { opens }, } ) }
- { clicksUnique + { uniqueClicks ? translate( 'Unique clicks: %(uniqueClicks)d (%(clickRate).2f%%)', { - args: { uniqueClicks: clicksUnique, clickRate: clicksRate }, + args: { uniqueClicks: uniqueClicks, clickRate: clicksRate }, } ) : translate( 'Clicks: %(clicks)d ', { args: { clicks }, diff --git a/client/state/stats/lists/utils.js b/client/state/stats/lists/utils.js index ea9438c6828f5..47989bca79b23 100644 --- a/client/state/stats/lists/utils.js +++ b/client/state/stats/lists/utils.js @@ -984,8 +984,8 @@ export const normalizers = { clicks, opens_rate, clicks_rate, - opens_unique, - clicks_unique, + unique_opens, + unique_clicks, total_sends, } ) => { const detailPage = site ? `/stats/email/opens/day/${ id }/${ site.slug }` : null; @@ -1000,8 +1000,8 @@ export const normalizers = { clicks: clicks || '0', opens_rate: opens_rate || '0', clicks_rate: clicks_rate || '0', - opens_unique: opens_unique || '0', - clicks_unique: clicks_unique || '0', + unique_opens: unique_opens || '0', + unique_clicks: unique_clicks || '0', total_sends: total_sends || '0', page: detailPage, actions: [ From 9dfd2d1247d140509993ea5acc82a4ae85a4e08f Mon Sep 17 00:00:00 2001 From: Miguel Lezama Date: Fri, 27 Dec 2024 07:29:42 -0300 Subject: [PATCH 04/10] n/a --- .../stats/stats-email-summary/index.jsx | 87 ++++++++++++------- 1 file changed, 57 insertions(+), 30 deletions(-) diff --git a/client/my-sites/stats/stats-email-summary/index.jsx b/client/my-sites/stats/stats-email-summary/index.jsx index f6b12f59eb905..dfa18963c04f7 100644 --- a/client/my-sites/stats/stats-email-summary/index.jsx +++ b/client/my-sites/stats/stats-email-summary/index.jsx @@ -19,9 +19,6 @@ const TooltipWrapper = ( { value, item, renderContent } ) => { const triggerRef = useRef( null ); const [ showTooltip, setShowTooltip ] = useState( false ); - const formattedValue = - typeof value === 'number' ? Number( value ).toFixed( 2 ) : value.replace( '%', '' ).trim(); - return ( { onMouseEnter={ () => setShowTooltip( true ) } onMouseLeave={ () => setShowTooltip( false ) } > - { `${ formattedValue }%` } + { value } { renderContent( item ) } @@ -63,14 +60,12 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { } const navigationItems = [ { label: backLabel, href: backLink }, { label: title } ]; - const renderTooltipContent = ( item ) => { - const uniqueOpens = parseInt( item.unique_opens, 10 ); - const uniqueClicks = parseInt( item.unique_clicks, 10 ); + const renderOpensTooltipContent = ( item ) => { + const opensUnique = parseInt( item.unique_opens, 10 ); const opens = parseInt( item.opens, 10 ); - const clicks = parseInt( item.clicks, 10 ); const opensRate = parseFloat( item.opens_rate ); - const clicksRate = parseFloat( item.clicks_rate ); const totalSends = parseInt( item.total_sends, 10 ); + const hasUniquesData = opensUnique > 0 && opens > 0; return (
@@ -80,22 +75,46 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { } ) }
- { uniqueOpens + { translate( 'Total opens: %(opens)d', { + args: { opens }, + } ) } +
+
+ { hasUniquesData ? translate( 'Unique opens: %(uniqueOpens)d (%(openRate).2f%%)', { - args: { uniqueOpens: uniqueOpens, openRate: opensRate }, + args: { uniqueOpens: opensUnique, openRate: opensRate }, } ) - : translate( 'Opens: %(opens)d)', { - args: { opens }, - } ) } + : translate( 'Unique opens: n/a' ) } +
+
+ ); + }; + + const renderClicksTooltipContent = ( item ) => { + const clicksUnique = parseInt( item.unique_clicks, 10 ); + const clicks = parseInt( item.clicks, 10 ); + const clicksRate = parseFloat( item.clicks_rate ); + const totalSends = parseInt( item.total_sends, 10 ); + const hasUniquesData = clicksUnique > 0 && clicks > 0; + + return ( +
+
+ { translate( 'Subscribers reached: %(sends)d', { + args: { sends: totalSends }, + } ) }
- { uniqueClicks + { translate( 'Total clicks: %(clicks)d', { + args: { clicks }, + } ) } +
+
+ { hasUniquesData ? translate( 'Unique clicks: %(uniqueClicks)d (%(clickRate).2f%%)', { - args: { uniqueClicks: uniqueClicks, clickRate: clicksRate }, + args: { uniqueClicks: clicksUnique, clickRate: clicksRate }, } ) - : translate( 'Clicks: %(clicks)d ', { - args: { clicks }, - } ) } + : translate( 'Unique clicks: n/a' ) }
); @@ -127,13 +146,18 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { { translate( 'Opens' ) } ), - body: ( item ) => ( - - ), + body: ( item ) => { + const opensUnique = parseInt( item.unique_opens, 10 ); + const opens = parseInt( item.opens, 10 ); + const hasUniquesData = opensUnique > 0 || opens === 0; + return ( + + ); + }, } } path="emails" moduleStrings={ { ...StatsStrings.emails, title: '' } } @@ -145,16 +169,19 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { metricLabel={ translate( 'Clicks' ) } valueField="clicks_rate" formatValue={ ( value, item ) => { - if ( item?.opens !== undefined ) { + if ( item?.clicks !== undefined ) { + const clicksUnique = parseInt( item.unique_clicks, 10 ); + const clicks = parseInt( item.clicks, 10 ); + const hasUniquesData = clicksUnique > 0 || clicks === 0; return ( ); } - return { `${ value }%` }; + return { value }; } } listItemClassName="stats__summary--narrow-mobile" /> From 4eaa7af63871052071f81460c2d733d799713ed2 Mon Sep 17 00:00:00 2001 From: Miguel Lezama Date: Fri, 27 Dec 2024 07:40:34 -0300 Subject: [PATCH 05/10] translators helper --- .../stats/stats-email-summary/index.jsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/client/my-sites/stats/stats-email-summary/index.jsx b/client/my-sites/stats/stats-email-summary/index.jsx index dfa18963c04f7..de8c9529d4bc9 100644 --- a/client/my-sites/stats/stats-email-summary/index.jsx +++ b/client/my-sites/stats/stats-email-summary/index.jsx @@ -152,7 +152,14 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { const hasUniquesData = opensUnique > 0 || opens === 0; return ( @@ -175,7 +182,14 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { const hasUniquesData = clicksUnique > 0 || clicks === 0; return ( From a043e43a24828ebded582002b42556ed3f5025e7 Mon Sep 17 00:00:00 2001 From: Miguel Lezama Date: Fri, 27 Dec 2024 07:59:03 -0300 Subject: [PATCH 06/10] add tooltip everywhere --- .../modules/stats-emails/stats-emails.tsx | 51 +++++++- .../modules/stats-emails/tooltips.tsx | 114 ++++++++++++++++++ .../stats/stats-email-summary/index.jsx | 92 ++------------ 3 files changed, 169 insertions(+), 88 deletions(-) create mode 100644 client/my-sites/stats/features/modules/stats-emails/tooltips.tsx diff --git a/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx b/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx index 0713504d75b94..91dc1bdc98058 100644 --- a/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx +++ b/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx @@ -19,6 +19,13 @@ import { useShouldGateStats } from '../../../hooks/use-should-gate-stats'; import StatsModule from '../../../stats-module'; import { StatsEmptyActionEmail } from '../shared'; import StatsCardSkeleton from '../shared/stats-card-skeleton'; +import { + TooltipWrapper, + createOpensTooltipContent, + createClicksTooltipContent, + hasUniqueMetrics, + EmailStatsItem, +} from './tooltips'; import type { StatsDefaultModuleProps, StatsStateProps } from '../types'; const StatsEmails: React.FC< StatsDefaultModuleProps > = ( { @@ -74,7 +81,25 @@ const StatsEmails: React.FC< StatsDefaultModuleProps > = ( { } additionalColumns={ { header: { translate( 'Opens' ) }, - body: ( item: { opens_rate: number } ) => { `${ item.opens_rate }%` }, + body: ( item: EmailStatsItem ) => { + const opensUnique = parseInt( item.unique_opens, 10 ); + const opens = parseInt( item.opens, 10 ); + const hasUniques = hasUniqueMetrics( opensUnique, opens ); + return ( + createOpensTooltipContent( item, translate ) } + /> + ); + }, } } moduleStrings={ moduleStrings } period={ period } @@ -83,8 +108,28 @@ const StatsEmails: React.FC< StatsDefaultModuleProps > = ( { mainItemLabel={ translate( 'Latest Emails' ) } metricLabel={ translate( 'Clicks' ) } valueField="clicks_rate" - formatValue={ ( value: number ) => `${ value }%` } - showSummaryLink + formatValue={ ( value: number, item: EmailStatsItem ) => { + if ( ! item?.opens ) { + return value; + } + const clicksUnique = parseInt( item.unique_clicks, 10 ); + const clicks = parseInt( item.clicks, 10 ); + const hasUniques = hasUniqueMetrics( clicksUnique, clicks ); + return ( + createClicksTooltipContent( item, translate ) } + /> + ); + } } className={ className } hasNoBackground skipQuery diff --git a/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx b/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx new file mode 100644 index 0000000000000..0e264a052cdb2 --- /dev/null +++ b/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx @@ -0,0 +1,114 @@ +import { Tooltip } from '@automattic/components'; +import { TranslateResult } from 'i18n-calypso'; +import React, { useRef, useState } from 'react'; + +export interface EmailStatsItem { + unique_opens: number; + opens: number; + opens_rate: number; + unique_clicks: number; + clicks: number; + clicks_rate: number; + total_sends: number; +} + +interface TooltipWrapperProps { + value: string; + item: EmailStatsItem; + renderContent: ( item: EmailStatsItem ) => React.ReactNode; +} + +export const TooltipWrapper: React.FC< TooltipWrapperProps > = ( { + value, + item, + renderContent, +} ) => { + const triggerRef = useRef< HTMLSpanElement >( null ); + const [ showTooltip, setShowTooltip ] = useState( false ); + + return ( + + setShowTooltip( true ) } + onMouseLeave={ () => setShowTooltip( false ) } + > + { value } + + + { renderContent( item ) } + + + ); +}; + +export const hasUniqueMetrics = ( uniqueValue: number, totalValue: number ) => { + return uniqueValue > 0 && totalValue > 0; +}; + +export const createOpensTooltipContent = ( + item: any, + translate: ( text: string, options?: { args: Record< string, any > } ) => TranslateResult +) => { + const opensUnique = parseInt( item.unique_opens, 10 ); + const opens = parseInt( item.opens, 10 ); + const opensRate = parseFloat( item.opens_rate ); + const totalSends = parseInt( item.total_sends, 10 ); + const hasUniques = hasUniqueMetrics( opensUnique, opens ); + + return ( +
+
+ { translate( 'Subscribers reached: %(sends)d', { + args: { sends: totalSends }, + } ) } +
+
+ { translate( 'Total opens: %(opens)d', { + args: { opens }, + } ) } +
+
+ { hasUniques + ? translate( 'Unique opens: %(uniqueOpens)d (%(openRate).2f%%)', { + args: { uniqueOpens: opensUnique, openRate: opensRate }, + } ) + : translate( 'Unique opens: n/a' ) } +
+
+ ); +}; + +export const createClicksTooltipContent = ( + item: any, + translate: ( text: string, options?: { args: Record< string, any > } ) => TranslateResult +) => { + const clicksUnique = parseInt( item.unique_clicks, 10 ); + const clicks = parseInt( item.clicks, 10 ); + const clicksRate = parseFloat( item.clicks_rate ); + const totalSends = parseInt( item.total_sends, 10 ); + const hasUniques = hasUniqueMetrics( clicksUnique, clicks ); + + return ( +
+
+ { translate( 'Subscribers reached: %(sends)d', { + args: { sends: totalSends }, + } ) } +
+
+ { translate( 'Total clicks: %(clicks)d', { + args: { clicks }, + } ) } +
+
+ { hasUniques + ? translate( 'Unique clicks: %(uniqueClicks)d (%(clickRate).2f%%)', { + args: { uniqueClicks: clicksUnique, clickRate: clicksRate }, + } ) + : translate( 'Unique clicks: n/a' ) } +
+
+ ); +}; diff --git a/client/my-sites/stats/stats-email-summary/index.jsx b/client/my-sites/stats/stats-email-summary/index.jsx index de8c9529d4bc9..4adfae719440b 100644 --- a/client/my-sites/stats/stats-email-summary/index.jsx +++ b/client/my-sites/stats/stats-email-summary/index.jsx @@ -1,12 +1,15 @@ -import { Tooltip } from '@automattic/components'; import { localize } from 'i18n-calypso'; -import { useRef, useState } from 'react'; import { connect } from 'react-redux'; import titlecase from 'to-title-case'; import JetpackColophon from 'calypso/components/jetpack-colophon'; import Main from 'calypso/components/main'; import NavigationHeader from 'calypso/components/navigation-header'; import { getSelectedSiteId, getSelectedSiteSlug } from 'calypso/state/ui/selectors'; +import { + TooltipWrapper, + createOpensTooltipContent, + createClicksTooltipContent, +} from '../features/modules/stats-emails/tooltips'; import StatsModule from '../stats-module'; import PageViewTracker from '../stats-page-view-tracker'; import statsStringsFactory from '../stats-strings'; @@ -15,27 +18,6 @@ import '../stats-module/summary-nav.scss'; const StatsStrings = statsStringsFactory(); -const TooltipWrapper = ( { value, item, renderContent } ) => { - const triggerRef = useRef( null ); - const [ showTooltip, setShowTooltip ] = useState( false ); - - return ( - - setShowTooltip( true ) } - onMouseLeave={ () => setShowTooltip( false ) } - > - { value } - - - { renderContent( item ) } - - - ); -}; - const StatsEmailSummary = ( { translate, period, siteSlug } ) => { // Navigation settings. One of the following, depending on the summary view. // Traffic => /stats/day/ @@ -60,66 +42,6 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { } const navigationItems = [ { label: backLabel, href: backLink }, { label: title } ]; - const renderOpensTooltipContent = ( item ) => { - const opensUnique = parseInt( item.unique_opens, 10 ); - const opens = parseInt( item.opens, 10 ); - const opensRate = parseFloat( item.opens_rate ); - const totalSends = parseInt( item.total_sends, 10 ); - const hasUniquesData = opensUnique > 0 && opens > 0; - - return ( -
-
- { translate( 'Subscribers reached: %(sends)d', { - args: { sends: totalSends }, - } ) } -
-
- { translate( 'Total opens: %(opens)d', { - args: { opens }, - } ) } -
-
- { hasUniquesData - ? translate( 'Unique opens: %(uniqueOpens)d (%(openRate).2f%%)', { - args: { uniqueOpens: opensUnique, openRate: opensRate }, - } ) - : translate( 'Unique opens: n/a' ) } -
-
- ); - }; - - const renderClicksTooltipContent = ( item ) => { - const clicksUnique = parseInt( item.unique_clicks, 10 ); - const clicks = parseInt( item.clicks, 10 ); - const clicksRate = parseFloat( item.clicks_rate ); - const totalSends = parseInt( item.total_sends, 10 ); - const hasUniquesData = clicksUnique > 0 && clicks > 0; - - return ( -
-
- { translate( 'Subscribers reached: %(sends)d', { - args: { sends: totalSends }, - } ) } -
-
- { translate( 'Total clicks: %(clicks)d', { - args: { clicks }, - } ) } -
-
- { hasUniquesData - ? translate( 'Unique clicks: %(uniqueClicks)d (%(clickRate).2f%%)', { - args: { uniqueClicks: clicksUnique, clickRate: clicksRate }, - } ) - : translate( 'Unique clicks: n/a' ) } -
-
- ); - }; - return (
{ ) } item={ item } - renderContent={ renderOpensTooltipContent } + renderContent={ createOpensTooltipContent } /> ); }, @@ -191,7 +113,7 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { ) } item={ item } - renderContent={ renderClicksTooltipContent } + renderContent={ createClicksTooltipContent } /> ); } From 2535b7049c3d69d90d3656b01eefe1f98221a2fc Mon Sep 17 00:00:00 2001 From: Miguel Lezama Date: Fri, 27 Dec 2024 15:41:25 -0300 Subject: [PATCH 07/10] make it easier to translate --- .../modules/stats-emails/stats-emails.tsx | 18 ++---------------- .../features/modules/stats-emails/tooltips.tsx | 8 ++++---- .../stats/stats-email-summary/index.jsx | 18 ++---------------- 3 files changed, 8 insertions(+), 36 deletions(-) diff --git a/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx b/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx index 91dc1bdc98058..dafed8750b87f 100644 --- a/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx +++ b/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx @@ -87,14 +87,7 @@ const StatsEmails: React.FC< StatsDefaultModuleProps > = ( { const hasUniques = hasUniqueMetrics( opensUnique, opens ); return ( createOpensTooltipContent( item, translate ) } /> @@ -117,14 +110,7 @@ const StatsEmails: React.FC< StatsDefaultModuleProps > = ( { const hasUniques = hasUniqueMetrics( clicksUnique, clicks ); return ( createClicksTooltipContent( item, translate ) } /> diff --git a/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx b/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx index 0e264a052cdb2..3f9cddd417f92 100644 --- a/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx +++ b/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx @@ -60,7 +60,7 @@ export const createOpensTooltipContent = ( return (
- { translate( 'Subscribers reached: %(sends)d', { + { translate( 'Recipients: %(sends)d', { args: { sends: totalSends }, } ) }
@@ -74,7 +74,7 @@ export const createOpensTooltipContent = ( ? translate( 'Unique opens: %(uniqueOpens)d (%(openRate).2f%%)', { args: { uniqueOpens: opensUnique, openRate: opensRate }, } ) - : translate( 'Unique opens: n/a' ) } + : translate( 'Unique opens: -' ) }
); @@ -93,7 +93,7 @@ export const createClicksTooltipContent = ( return (
- { translate( 'Subscribers reached: %(sends)d', { + { translate( 'Recipients: %(sends)d', { args: { sends: totalSends }, } ) }
@@ -107,7 +107,7 @@ export const createClicksTooltipContent = ( ? translate( 'Unique clicks: %(uniqueClicks)d (%(clickRate).2f%%)', { args: { uniqueClicks: clicksUnique, clickRate: clicksRate }, } ) - : translate( 'Unique clicks: n/a' ) } + : translate( 'Unique clicks: -' ) }
); diff --git a/client/my-sites/stats/stats-email-summary/index.jsx b/client/my-sites/stats/stats-email-summary/index.jsx index 4adfae719440b..89713128fe396 100644 --- a/client/my-sites/stats/stats-email-summary/index.jsx +++ b/client/my-sites/stats/stats-email-summary/index.jsx @@ -74,14 +74,7 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { const hasUniquesData = opensUnique > 0 || opens === 0; return ( @@ -104,14 +97,7 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { const hasUniquesData = clicksUnique > 0 || clicks === 0; return ( From bcc100fa80239fc97f6be493e87e4cc3db10b1bd Mon Sep 17 00:00:00 2001 From: Miguel Lezama Date: Fri, 27 Dec 2024 15:45:09 -0300 Subject: [PATCH 08/10] =?UTF-8?q?swap=20to=20=E2=80=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stats/features/modules/stats-emails/stats-emails.tsx | 4 ++-- .../my-sites/stats/features/modules/stats-emails/tooltips.tsx | 4 ++-- client/my-sites/stats/stats-email-summary/index.jsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx b/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx index dafed8750b87f..ba0166dc8f8bb 100644 --- a/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx +++ b/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx @@ -87,7 +87,7 @@ const StatsEmails: React.FC< StatsDefaultModuleProps > = ( { const hasUniques = hasUniqueMetrics( opensUnique, opens ); return ( createOpensTooltipContent( item, translate ) } /> @@ -110,7 +110,7 @@ const StatsEmails: React.FC< StatsDefaultModuleProps > = ( { const hasUniques = hasUniqueMetrics( clicksUnique, clicks ); return ( createClicksTooltipContent( item, translate ) } /> diff --git a/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx b/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx index 3f9cddd417f92..191d59dbd7aa1 100644 --- a/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx +++ b/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx @@ -74,7 +74,7 @@ export const createOpensTooltipContent = ( ? translate( 'Unique opens: %(uniqueOpens)d (%(openRate).2f%%)', { args: { uniqueOpens: opensUnique, openRate: opensRate }, } ) - : translate( 'Unique opens: -' ) } + : translate( 'Unique opens: —' ) } ); @@ -107,7 +107,7 @@ export const createClicksTooltipContent = ( ? translate( 'Unique clicks: %(uniqueClicks)d (%(clickRate).2f%%)', { args: { uniqueClicks: clicksUnique, clickRate: clicksRate }, } ) - : translate( 'Unique clicks: -' ) } + : translate( 'Unique clicks: —' ) } ); diff --git a/client/my-sites/stats/stats-email-summary/index.jsx b/client/my-sites/stats/stats-email-summary/index.jsx index 89713128fe396..c5da529e7633e 100644 --- a/client/my-sites/stats/stats-email-summary/index.jsx +++ b/client/my-sites/stats/stats-email-summary/index.jsx @@ -74,7 +74,7 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { const hasUniquesData = opensUnique > 0 || opens === 0; return ( @@ -97,7 +97,7 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { const hasUniquesData = clicksUnique > 0 || clicks === 0; return ( From 3030eaba8386c2376375d09022ab17b27c5a0cec Mon Sep 17 00:00:00 2001 From: Miguel Lezama Date: Fri, 27 Dec 2024 15:57:31 -0300 Subject: [PATCH 09/10] simplify --- .../modules/stats-emails/stats-emails.tsx | 12 ++++---- .../modules/stats-emails/tooltips.tsx | 30 +++++++++++-------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx b/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx index ba0166dc8f8bb..b837582708d5a 100644 --- a/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx +++ b/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx @@ -82,14 +82,14 @@ const StatsEmails: React.FC< StatsDefaultModuleProps > = ( { additionalColumns={ { header: { translate( 'Opens' ) }, body: ( item: EmailStatsItem ) => { - const opensUnique = parseInt( item.unique_opens, 10 ); - const opens = parseInt( item.opens, 10 ); + const opensUnique = parseInt( String( item.unique_opens ), 10 ); + const opens = parseInt( String( item.opens ), 10 ); const hasUniques = hasUniqueMetrics( opensUnique, opens ); return ( createOpensTooltipContent( item, translate ) } + renderContent={ createOpensTooltipContent } /> ); }, @@ -105,14 +105,14 @@ const StatsEmails: React.FC< StatsDefaultModuleProps > = ( { if ( ! item?.opens ) { return value; } - const clicksUnique = parseInt( item.unique_clicks, 10 ); - const clicks = parseInt( item.clicks, 10 ); + const clicksUnique = parseInt( String( item.unique_clicks ), 10 ); + const clicks = parseInt( String( item.clicks ), 10 ); const hasUniques = hasUniqueMetrics( clicksUnique, clicks ); return ( createClicksTooltipContent( item, translate ) } + renderContent={ createClicksTooltipContent } /> ); } } diff --git a/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx b/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx index 191d59dbd7aa1..a4d97d7e81f84 100644 --- a/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx +++ b/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx @@ -1,5 +1,5 @@ import { Tooltip } from '@automattic/components'; -import { TranslateResult } from 'i18n-calypso'; +import { TranslateResult, useTranslate } from 'i18n-calypso'; import React, { useRef, useState } from 'react'; export interface EmailStatsItem { @@ -15,7 +15,10 @@ export interface EmailStatsItem { interface TooltipWrapperProps { value: string; item: EmailStatsItem; - renderContent: ( item: EmailStatsItem ) => React.ReactNode; + renderContent: ( + item: EmailStatsItem, + translate: ( text: string, options?: { args: Record< string, any > } ) => TranslateResult + ) => React.ReactNode; } export const TooltipWrapper: React.FC< TooltipWrapperProps > = ( { @@ -25,6 +28,7 @@ export const TooltipWrapper: React.FC< TooltipWrapperProps > = ( { } ) => { const triggerRef = useRef< HTMLSpanElement >( null ); const [ showTooltip, setShowTooltip ] = useState( false ); + const translate = useTranslate(); return ( @@ -37,7 +41,7 @@ export const TooltipWrapper: React.FC< TooltipWrapperProps > = ( { { value } - { renderContent( item ) } + { renderContent( item, translate ) } ); @@ -48,13 +52,13 @@ export const hasUniqueMetrics = ( uniqueValue: number, totalValue: number ) => { }; export const createOpensTooltipContent = ( - item: any, + item: EmailStatsItem, translate: ( text: string, options?: { args: Record< string, any > } ) => TranslateResult ) => { - const opensUnique = parseInt( item.unique_opens, 10 ); - const opens = parseInt( item.opens, 10 ); - const opensRate = parseFloat( item.opens_rate ); - const totalSends = parseInt( item.total_sends, 10 ); + const opensUnique = parseInt( String( item.unique_opens ), 10 ); + const opens = parseInt( String( item.opens ), 10 ); + const opensRate = parseFloat( String( item.opens_rate ) ); + const totalSends = parseInt( String( item.total_sends ), 10 ); const hasUniques = hasUniqueMetrics( opensUnique, opens ); return ( @@ -81,13 +85,13 @@ export const createOpensTooltipContent = ( }; export const createClicksTooltipContent = ( - item: any, + item: EmailStatsItem, translate: ( text: string, options?: { args: Record< string, any > } ) => TranslateResult ) => { - const clicksUnique = parseInt( item.unique_clicks, 10 ); - const clicks = parseInt( item.clicks, 10 ); - const clicksRate = parseFloat( item.clicks_rate ); - const totalSends = parseInt( item.total_sends, 10 ); + const clicksUnique = parseInt( String( item.unique_clicks ), 10 ); + const clicks = parseInt( String( item.clicks ), 10 ); + const clicksRate = parseFloat( String( item.clicks_rate ) ); + const totalSends = parseInt( String( item.total_sends ), 10 ); const hasUniques = hasUniqueMetrics( clicksUnique, clicks ); return ( From ea07b8938358100ac80bda5aa23d7708d4419740 Mon Sep 17 00:00:00 2001 From: Miguel Lezama Date: Fri, 27 Dec 2024 16:05:40 -0300 Subject: [PATCH 10/10] use components --- .../modules/stats-emails/stats-emails.tsx | 8 +++---- .../modules/stats-emails/tooltips.tsx | 24 +++++++------------ .../stats/stats-email-summary/index.jsx | 8 +++---- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx b/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx index b837582708d5a..f410e09fa8d6a 100644 --- a/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx +++ b/client/my-sites/stats/features/modules/stats-emails/stats-emails.tsx @@ -21,8 +21,8 @@ import { StatsEmptyActionEmail } from '../shared'; import StatsCardSkeleton from '../shared/stats-card-skeleton'; import { TooltipWrapper, - createOpensTooltipContent, - createClicksTooltipContent, + OpensTooltipContent, + ClicksTooltipContent, hasUniqueMetrics, EmailStatsItem, } from './tooltips'; @@ -89,7 +89,7 @@ const StatsEmails: React.FC< StatsDefaultModuleProps > = ( { ); }, @@ -112,7 +112,7 @@ const StatsEmails: React.FC< StatsDefaultModuleProps > = ( { ); } } diff --git a/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx b/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx index a4d97d7e81f84..945106ce4ca02 100644 --- a/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx +++ b/client/my-sites/stats/features/modules/stats-emails/tooltips.tsx @@ -1,5 +1,5 @@ import { Tooltip } from '@automattic/components'; -import { TranslateResult, useTranslate } from 'i18n-calypso'; +import { useTranslate } from 'i18n-calypso'; import React, { useRef, useState } from 'react'; export interface EmailStatsItem { @@ -15,20 +15,16 @@ export interface EmailStatsItem { interface TooltipWrapperProps { value: string; item: EmailStatsItem; - renderContent: ( - item: EmailStatsItem, - translate: ( text: string, options?: { args: Record< string, any > } ) => TranslateResult - ) => React.ReactNode; + TooltipContent: React.ComponentType< { item: EmailStatsItem } >; } export const TooltipWrapper: React.FC< TooltipWrapperProps > = ( { value, item, - renderContent, + TooltipContent, } ) => { const triggerRef = useRef< HTMLSpanElement >( null ); const [ showTooltip, setShowTooltip ] = useState( false ); - const translate = useTranslate(); return ( @@ -41,7 +37,7 @@ export const TooltipWrapper: React.FC< TooltipWrapperProps > = ( { { value } - { renderContent( item, translate ) } + ); @@ -51,10 +47,8 @@ export const hasUniqueMetrics = ( uniqueValue: number, totalValue: number ) => { return uniqueValue > 0 && totalValue > 0; }; -export const createOpensTooltipContent = ( - item: EmailStatsItem, - translate: ( text: string, options?: { args: Record< string, any > } ) => TranslateResult -) => { +export const OpensTooltipContent: React.FC< { item: EmailStatsItem } > = ( { item } ) => { + const translate = useTranslate(); const opensUnique = parseInt( String( item.unique_opens ), 10 ); const opens = parseInt( String( item.opens ), 10 ); const opensRate = parseFloat( String( item.opens_rate ) ); @@ -84,10 +78,8 @@ export const createOpensTooltipContent = ( ); }; -export const createClicksTooltipContent = ( - item: EmailStatsItem, - translate: ( text: string, options?: { args: Record< string, any > } ) => TranslateResult -) => { +export const ClicksTooltipContent: React.FC< { item: EmailStatsItem } > = ( { item } ) => { + const translate = useTranslate(); const clicksUnique = parseInt( String( item.unique_clicks ), 10 ); const clicks = parseInt( String( item.clicks ), 10 ); const clicksRate = parseFloat( String( item.clicks_rate ) ); diff --git a/client/my-sites/stats/stats-email-summary/index.jsx b/client/my-sites/stats/stats-email-summary/index.jsx index c5da529e7633e..0bd8c091a49d3 100644 --- a/client/my-sites/stats/stats-email-summary/index.jsx +++ b/client/my-sites/stats/stats-email-summary/index.jsx @@ -7,8 +7,8 @@ import NavigationHeader from 'calypso/components/navigation-header'; import { getSelectedSiteId, getSelectedSiteSlug } from 'calypso/state/ui/selectors'; import { TooltipWrapper, - createOpensTooltipContent, - createClicksTooltipContent, + OpensTooltipContent, + ClicksTooltipContent, } from '../features/modules/stats-emails/tooltips'; import StatsModule from '../stats-module'; import PageViewTracker from '../stats-page-view-tracker'; @@ -76,7 +76,7 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { ); }, @@ -99,7 +99,7 @@ const StatsEmailSummary = ( { translate, period, siteSlug } ) => { ); }