Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,12 @@ import {
import { ERROR_CODE_MISSING_REQUIRED_SCOPE } from '@/js/util/errors';
import BadgeWithTooltip from '@/js/components/BadgeWithTooltip';
import AudienceTilePagesMetricContent from './AudienceTilePagesMetricContent';
import AudienceErrorModal from '@/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceErrorModal';
import { AREA_MAIN_DASHBOARD_TRAFFIC_AUDIENCE_SEGMENTATION } from '@/js/googlesitekit/widgets/default-areas';
import useViewContext from '@/js/hooks/useViewContext';
import { trackEvent } from '@/js/util';
import useFormValue from '@/js/hooks/useFormValue';

export default function AudienceTilePagesMetric( {
// TODO: The prop `audienceTileNumber` is part of a temporary workaround to ensure `AudienceErrorModal` is only rendered once
// within `AudienceTilesWidget`. This should be removed once the `AudienceErrorModal` render is extracted
// from `AudienceTilePagesMetric` and it's rendered once at a higher level instead. See https://github.com/google/site-kit-wp/issues/9543.
audienceTileNumber,
audienceSlug,
TileIcon,
title,
Expand Down Expand Up @@ -107,37 +102,25 @@ export default function AudienceTilePagesMetric( {
select( MODULES_ANALYTICS_4 ).isFetchingSyncAvailableCustomDimensions()
);

const customDimensionError = useSelect( ( select ) =>
// Error state handled at parent level; keep selector side-effects minimal if needed.
useSelect( ( select ) =>
select( MODULES_ANALYTICS_4 ).getCreateCustomDimensionError(
postTypeDimension
)
);

const propertyID = useSelect( ( select ) =>
select( MODULES_ANALYTICS_4 ).getPropertyID()
);

const { clearError } = useDispatch( MODULES_ANALYTICS_4 );
const { setValues } = useDispatch( CORE_FORMS );
const { setPermissionScopeError, clearPermissionScopeError } =
useDispatch( CORE_USER );
const { setPermissionScopeError } = useDispatch( CORE_USER );

const isRetryingCustomDimensionCreate = useFormValue(
AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE,
'isRetrying'
);

const autoSubmit = useFormValue(
AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE,
'autoSubmit'
);
// Retry state now lifted to parent component.
useFormValue( AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE, 'isRetrying' );

const setupErrorCode = useSelect( ( select ) =>
select( CORE_SITE ).getSetupErrorCode()
);
const { setSetupErrorCode } = useDispatch( CORE_SITE );
useFormValue( AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE, 'autoSubmit' );
useSelect( ( select ) => select( CORE_SITE ).getSetupErrorCode() );
useDispatch( CORE_SITE ); // retain dispatch instance (no local usage after refactor)

const hasOAuthError = autoSubmit && setupErrorCode === 'access_denied';
// OAuth error handled by parent; local component no longer consumes this.
// OAuth error previously used for modal; no longer needed after modal extraction.

const onCreateCustomDimension = useCallback(
( { isRetrying } = {} ) => {
Expand Down Expand Up @@ -173,24 +156,7 @@ export default function AudienceTilePagesMetric( {
]
);

const onCancel = useCallback( () => {
setValues( AUDIENCE_TILE_CUSTOM_DIMENSION_CREATE, {
autoSubmit: false,
isRetrying: false,
} );
setSetupErrorCode( null );
clearPermissionScopeError();
clearError( 'createCustomDimension', [
propertyID,
CUSTOM_DIMENSION_DEFINITIONS.googlesitekit_post_type,
] );
}, [
clearError,
clearPermissionScopeError,
propertyID,
setSetupErrorCode,
setValues,
] );
// Cancel handler moved to parent for centralized modal control.

const isMobileBreakpoint = [ BREAKPOINT_SMALL, BREAKPOINT_TABLET ].includes(
breakpoint
Expand Down Expand Up @@ -235,42 +201,12 @@ export default function AudienceTilePagesMetric( {
onCreateCustomDimension={ onCreateCustomDimension }
isSaving={ isSaving }
/>
{ /*
TODO: The `audienceTileNumber` check is part of a temporary workaround to ensure `AudienceErrorModal` is only rendered once
within `AudienceTilesWidget`. This should be removed, and the `AudienceErrorModal` render extracted
from here to be rendered once at a higher level instead. See https://github.com/google/site-kit-wp/issues/9543.
*/ }
{ audienceTileNumber === 0 &&
( ( customDimensionError && ! isSaving ) ||
( isRetryingCustomDimensionCreate &&
! isAutoCreatingCustomDimensionsForAudience ) ||
hasOAuthError ) && (
<AudienceErrorModal
apiErrors={ [ customDimensionError ] }
title={ __(
'Failed to enable metric',
'google-site-kit'
) }
description={ __(
'Oops! Something went wrong. Retry enabling the metric.',
'google-site-kit'
) }
onRetry={ () =>
onCreateCustomDimension( { isRetrying: true } )
}
onCancel={ onCancel }
inProgress={ isSaving }
hasOAuthError={ hasOAuthError }
trackEventCategory={ `${ viewContext }_audiences-top-content-cta` }
/>
) }
</div>
</div>
);
}

AudienceTilePagesMetric.propTypes = {
audienceTileNumber: PropTypes.number,
audienceSlug: PropTypes.string.isRequired,
TileIcon: PropTypes.elementType.isRequired,
title: PropTypes.string.isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ import BadgeWithTooltip from '@/js/components/BadgeWithTooltip';
import useViewContext from '@/js/hooks/useViewContext';
import AudienceTileZeroData from './AudienceTileZeroData';
export default function AudienceTile( {
// TODO: The prop `audienceTileNumber` is part of a temporary workaround to ensure `AudienceErrorModal` is only rendered once
// within `AudienceTilesWidget`. This should be removed once the `AudienceErrorModal` render is extracted
// from `AudienceTilePagesMetric` and it's rendered once at a higher level instead. See https://github.com/google/site-kit-wp/issues/9543.
audienceTileNumber = 0,
audienceSlug,
title,
infoTooltip,
Expand Down Expand Up @@ -284,7 +280,6 @@ export default function AudienceTile( {
( postTypeDimensionExists &&
! hasInvalidCustomDimensionError ) ) && (
<AudienceTilePagesMetric
audienceTileNumber={ audienceTileNumber }
audienceSlug={ audienceSlug }
TileIcon={ AudienceMetricIconTopContent }
title={ __(
Expand All @@ -303,7 +298,6 @@ export default function AudienceTile( {
}

AudienceTile.propTypes = {
audienceTileNumber: PropTypes.number,
audienceSlug: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
infoTooltip: PropTypes.oneOfType( [ PropTypes.string, PropTypes.element ] ),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import PropTypes from 'prop-types';
import { Fragment } from '@wordpress/element';
import {
BREAKPOINT_SMALL,
BREAKPOINT_TABLET,
useBreakpoint,
} from '@/js/hooks/useBreakpoint';
import AudienceTileLoading from '@/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileLoading';
import AudienceTileError from '@/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile/AudienceTileError';
import AudienceTile from '@/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTile';
import AudienceTooltipMessage from '@/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceTilesWidget/AudienceTooltipMessage';
import { reportRowsWithSetValues } from '@/js/modules/analytics-4/utils/report-rows-with-set-values';

function AudienceTilesList( props ) {
const {
activeTileIndex,
visibleAudiences,
loading,
topCitiesReportsLoaded,
topContentReportsLoaded,
topContentPageTitlesReportsLoaded,
individualTileErrors,
totalPageviews,
hasInvalidCustomDimensionError,
Widget,
getAudienceTileData,
handleDismiss,
} = props;

// Determine tabbed breakpoint locally to reduce prop drilling.
const breakpoint = useBreakpoint();
const isTabbedBreakpoint =
breakpoint === BREAKPOINT_SMALL || breakpoint === BREAKPOINT_TABLET;

return (
<Fragment>
{ visibleAudiences.map( ( audienceResourceName, index ) => {
// Respect tabbed breakpoint visibility rules.
if ( isTabbedBreakpoint && index !== activeTileIndex ) {
return null;
}

const tileData = getAudienceTileData(
audienceResourceName,
index
);
const {
audienceName,
audienceSlug,
visitors,
prevVisitors,
visitsPerVisitors,
prevVisitsPerVisitors,
pagesPerVisit,
prevPagesPerVisit,
pageviews,
prevPageviews,
topCities,
topContent,
topContentTitles,
isZeroData,
isPartialData,
} = tileData;

// While reports or zero/partial flags are still undefined, show loading state.
const reportsNotReady =
loading ||
! topCitiesReportsLoaded?.[ audienceResourceName ] ||
! topContentReportsLoaded?.[ audienceResourceName ] ||
! topContentPageTitlesReportsLoaded?.[
audienceResourceName
] ||
isZeroData === undefined ||
isPartialData === undefined;

if ( reportsNotReady ) {
return (
<Widget key={ audienceResourceName } noPadding>
<AudienceTileLoading />
</Widget>
);
}

// Show per-tile error component if errors exist.
const perTileErrors =
individualTileErrors?.[ audienceResourceName ];
if ( perTileErrors?.length > 0 ) {
return (
<AudienceTileError
key={ audienceResourceName }
audienceSlug={ audienceSlug }
errors={ perTileErrors }
/>
);
}

// Filter out rows with unset values for top cities.
const filteredTopCitiesRows = topCities?.rows
? reportRowsWithSetValues( topCities.rows )
: [];

// Build top cities structure (limit to first 3 entries).
const topCitiesProp = {
dimensionValues: [
filteredTopCitiesRows?.[ 0 ]?.dimensionValues?.[ 0 ],
filteredTopCitiesRows?.[ 1 ]?.dimensionValues?.[ 0 ],
filteredTopCitiesRows?.[ 2 ]?.dimensionValues?.[ 0 ],
],
metricValues: [
filteredTopCitiesRows?.[ 0 ]?.metricValues?.[ 0 ],
filteredTopCitiesRows?.[ 1 ]?.metricValues?.[ 0 ],
filteredTopCitiesRows?.[ 2 ]?.metricValues?.[ 0 ],
],
total: visitors,
};

// Build top content structure (limit to first 3 rows).
const topContentProp = {
dimensionValues: [
topContent?.rows?.[ 0 ]?.dimensionValues?.[ 0 ],
topContent?.rows?.[ 1 ]?.dimensionValues?.[ 0 ],
topContent?.rows?.[ 2 ]?.dimensionValues?.[ 0 ],
],
metricValues: [
topContent?.rows?.[ 0 ]?.metricValues?.[ 0 ],
topContent?.rows?.[ 1 ]?.metricValues?.[ 0 ],
topContent?.rows?.[ 2 ]?.metricValues?.[ 0 ],
],
};

return (
<AudienceTile
key={ audienceResourceName }
audienceSlug={ audienceSlug }
title={ audienceName }
infoTooltip={
<AudienceTooltipMessage
audienceName={ audienceName }
audienceSlug={ audienceSlug }
/>
}
visitors={ {
currentValue: visitors,
previousValue: prevVisitors,
} }
visitsPerVisitor={ {
currentValue: visitsPerVisitors,
previousValue: prevVisitsPerVisitors,
} }
pagesPerVisit={ {
currentValue: pagesPerVisit,
previousValue: prevPagesPerVisit,
} }
pageviews={ {
currentValue: pageviews,
previousValue: prevPageviews,
} }
percentageOfTotalPageViews={
totalPageviews !== 0
? pageviews / totalPageviews
: 0
}
topCities={ topCitiesProp }
topContent={ topContentProp }
topContentTitles={ topContentTitles }
hasInvalidCustomDimensionError={
hasInvalidCustomDimensionError
}
Widget={ Widget }
audienceResourceName={ audienceResourceName }
isZeroData={ isZeroData }
isPartialData={ isPartialData }
isTileHideable={ visibleAudiences.length > 1 }
onHideTile={ () =>
handleDismiss( audienceResourceName )
}
/>
);
} ) }
</Fragment>
);
}

AudienceTilesList.propTypes = {
activeTileIndex: PropTypes.number.isRequired,
visibleAudiences: PropTypes.array.isRequired,
loading: PropTypes.bool.isRequired,
topCitiesReportsLoaded: PropTypes.object.isRequired,
topContentReportsLoaded: PropTypes.object.isRequired,
topContentPageTitlesReportsLoaded: PropTypes.object.isRequired,
individualTileErrors: PropTypes.object.isRequired,
totalPageviews: PropTypes.number.isRequired,
hasInvalidCustomDimensionError: PropTypes.bool.isRequired,
Widget: PropTypes.elementType.isRequired,
getAudienceTileData: PropTypes.func.isRequired,
handleDismiss: PropTypes.func.isRequired,
};

export default AudienceTilesList;
Loading
Loading