import { Button } from '@wordpress/components';
import { useState, useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';
import {
defaultFrequencyOptions,
defaultFrequencyValue,
defaultDateRangeOptions,
defaultDateRangeValue,
getPersonalizationAnalysisData,
formatString,
} from '../../shared/utils';
import Analysis from '../../shared/Analysis';
import AnalysisHeader from '../../shared/AnalysisHeader';
import AnalysisHeaderContent from '../../shared/AnalysisHeaderContent';
import AnalysisHeaderActions from '../../shared/AnalysisHeaderActions';
import AnalysisContent from '../../shared/AnalysisContent';
import AnalysisGraphAndSummary from '../../shared/AnalysisGraphAndSummary';
import AnalysisGraph from '../../shared/AnalysisGraph';
import AnalysisTrend from '../../shared/AnalysisTrend';
import AnalysisFilters from '../../shared/AnalysisFilters';
import AnalysisSummary from '../../shared/AnalysisSummary';
import AnalysisSummaryHeadline from '../../shared/AnalysisSummaryHeadline';
import AnalysisSummaryRow from '../../shared/AnalysisSummaryRow';
import NoDataPlaceholder from '../../shared/NoDataPlaceholder';
const DEFAULT_DATE_RANGE_VALUE = defaultDateRangeValue();
const DATE_RANGE_OPTIONS = Object.entries( defaultDateRangeOptions() ).map(
( [ key, value ] ) => {
return {
label: value,
value: key,
};
}
);
const DEFAULT_FREQUENCY_VALUE = defaultFrequencyValue();
const FREQUENCY_OPTIONS = Object.entries( defaultFrequencyOptions() ).map(
( [ key, value ] ) => {
return {
label: value,
value: key,
};
}
);
function sprintf( format, ...args ) {
let i = 0;
return format.replace( /%s/g, () => args[ i++ ] );
}
function PagePersonalizationFilters( props ) {
const { frequency, setFrequency, dateRange, setDateRange } = props;
return (
<>
{ frequency && (
{ FREQUENCY_OPTIONS.map( ( option ) => (
) ) }
) }
{ dateRange && (
{ DATE_RANGE_OPTIONS.map( ( option ) => (
) ) }
) }
>
);
}
function PersonalizationTableRow( props ) {
const { variant, frequency, setFrequency, dateRange, setDateRange } = props;
const [ isExpanded, setIsExpanded ] = useState( false );
return (
<>
{ isExpanded && (
{ __( 'Views', 'liana-with-growthstack' ) }
{ __(
'Tracks all views for this variant.',
'liana-with-growthstack'
) }
{ variant.views.summaryRows.map( ( row ) => (
) ) }
item.label
) }
data={ [
{
label: __( 'Views', 'liana-with-growthstack' ),
data: variant.views.graphData.map(
( item ) => item.value
),
fill: false,
backgroundColor: '#007CBB',
borderColor: '#007CBB',
lineTension: 0.33,
},
] }
/>
{ __( 'Clicks', 'liana-with-growthstack' ) }
{ __(
'Tracks clicks for clickable elements inside this variant.',
'liana-with-growthstack'
) }
{ variant.clicks.summaryRows.map( ( row ) => (
) ) }
item.label
) }
data={ [
{
label: __( 'Clicks', 'liana-with-growthstack' ),
data: variant.clicks.graphData.map(
( item ) => item.value
),
fill: false,
backgroundColor: '#007CBB',
borderColor: '#007CBB',
lineTension: 0.33,
},
] }
/>
) }
>
);
}
type PersonalizationVariant = {
id: string;
name: string;
views: PersonalizationAnalyisResult;
clicks: PersonalizationAnalyisResult;
};
type Personalization = {
id: string;
name: string;
variants: PersonalizationVariant[];
};
export default function PagePersonalization( props ) {
const {
getCachedData = null,
setCachedData = null,
postId,
editLink,
focusPersonalizationId = null,
focusVariantTitlesToOverride = null,
hasABTests,
isEditor = false,
} = props;
const [ dateRange, setDateRange ] = useState( DEFAULT_DATE_RANGE_VALUE );
const [ frequency, setFrequency ] = useState( DEFAULT_FREQUENCY_VALUE );
const [ personalizations, setPersonalizations ] = useState( null );
const [ hasPersonalizations, setHasPersonalizations ] = useState( false );
const [ showAdvanced, setShowAdvanced ] = useState( [] ); // needs to be array as there are multiple personalizations
const [ data, setData ] = useState( null );
const [ error, setError ] = useState( null );
const ENDPOINT = `/growthstack/v1/analytics/page/${ postId }/personalization/`;
useEffect( () => {
const cached = getCachedData ? getCachedData( ENDPOINT ) : null;
if ( cached?.isFetched ) {
setData( cached.data );
setError( cached.error );
return;
}
setError( null );
apiFetch( { path: ENDPOINT } )
.then( ( response ) => {
const responseData = response?.data?.data || [];
setData( responseData );
if ( setCachedData ) {
setCachedData( ENDPOINT, responseData, null );
}
} )
.catch( ( fetchError ) => {
setError( fetchError );
if ( setCachedData ) {
setCachedData( ENDPOINT, null, fetchError );
}
// eslint-disable-next-line no-console
console.error(
'Failed to fetch page personalization data',
fetchError
);
} );
}, [ getCachedData, setCachedData, postId, ENDPOINT ] );
useEffect( () => {
if ( data ) {
const _personalizations: Personalization[] = [];
let rawPersonalizationsData = data || {};
// In editor, filter out personalizations that are not the focus
if ( focusPersonalizationId !== null ) {
const filteredPersonalizations = {};
Object.entries( rawPersonalizationsData ).forEach(
( [ personalizationId, rawDataPointsVariants ] ) => {
if ( personalizationId === focusPersonalizationId ) {
filteredPersonalizations[ personalizationId ] =
rawDataPointsVariants;
}
}
);
rawPersonalizationsData = filteredPersonalizations;
}
let personalizationNumber = 1;
const hasOnlyOnePersonalization =
Object.keys( rawPersonalizationsData ).length === 1;
Object.entries( rawPersonalizationsData ).forEach(
( [ personalizationId, personalizationData ] ) => {
let personalizationTitle =
// @ts-ignore
personalizationData?.title || null;
const rawDataPointsVariants =
// @ts-ignore
personalizationData?.variants || {};
const variants = Object.entries(
rawDataPointsVariants as { [ key: string ]: any }
).map( ( [ variantId, variantData ] ) => {
let variantTitle = variantData?.title || variantId;
const rawDataPoints = variantData?.data || {};
// get raw data points from other variants
const otherVariantsRawDataPoints: any[] = [];
Object.entries(
rawDataPointsVariants as { [ key: string ]: any }
).forEach( ( [ otherVariantId, otherVariantData ] ) => {
if ( otherVariantId !== variantId ) {
otherVariantsRawDataPoints.push(
otherVariantData?.data || {}
);
}
} );
const views = getPersonalizationAnalysisData(
rawDataPoints as PageRawDataPoints,
otherVariantsRawDataPoints,
'view_count',
dateRange,
frequency
);
const clicks = getPersonalizationAnalysisData(
rawDataPoints as PageRawDataPoints,
otherVariantsRawDataPoints,
'click_count',
dateRange,
frequency
);
let clicksPerViews = 0;
if (
views?.insights?.sum?.value &&
views?.insights?.sum?.value > 0 &&
clicks?.insights?.sum?.value &&
clicks?.insights?.sum?.value > 0
) {
clicksPerViews =
( clicks.insights.sum.value /
views.insights.sum.value ) *
100;
}
if (
focusVariantTitlesToOverride !== null &&
focusVariantTitlesToOverride[ variantId ]
) {
variantTitle =
focusVariantTitlesToOverride[ variantId ];
}
if ( variantTitle === '' ) {
variantTitle = variantId;
}
let rules = [];
if ( variantData?.rules ) {
rules = variantData.rules.map( ( rule ) => {
return rule.name;
} );
}
return {
id: variantId,
name: variantTitle,
rules,
views,
clicks,
clicksPerViews,
};
} );
if ( variants.length > 0 ) {
if ( personalizationTitle === null ) {
personalizationTitle = hasOnlyOnePersonalization
? __( 'Personalization', 'liana-with-growthstack' )
: formatString(
// translators: 1: personalization number.
__(
'Personalization %s',
'liana-with-growthstack'
),
personalizationNumber
);
}
_personalizations.push( {
id: personalizationId,
name: personalizationTitle,
variants: variants as PersonalizationVariant[],
} );
personalizationNumber++;
}
}
);
if ( _personalizations.length > 0 ) {
setHasPersonalizations( true );
}
setPersonalizations( _personalizations );
}
}, [
data,
dateRange,
focusPersonalizationId,
focusVariantTitlesToOverride,
frequency,
] );
const isLoading = ! data && ! error;
return (
{ isLoading &&
}
{ ! isLoading && ! hasPersonalizations && (
{ hasPersonalizations && (
{ __( 'Personalizations', 'liana-with-growthstack' ) }
) }
{ ! hasABTests && ! isEditor && (
<>
{ __(
'No personalizations on this page',
'liana-with-growthstack'
) }
{ __(
'You can create personalizations for this page to show different content to different visitors.',
'liana-with-growthstack'
) }
>
) }
{ ! hasABTests && isEditor && (
<>
{ __(
'No analytics data yet',
'liana-with-growthstack'
) }
{ __(
'Analytics data will appear here once visitors start seeing this personalization on your published page.',
'liana-with-growthstack'
) }
>
) }
{ hasABTests && ! isEditor && (
<>
{ __(
'No personalizations on this page',
'liana-with-growthstack'
) }
{ __(
'You can create personalizations for this page to show different content to different visitors.',
'liana-with-growthstack'
) }
{ __(
"You can't create personalizations while an A/B test is running on this page.",
'liana-with-growthstack'
) }
>
) }
{ hasABTests && isEditor && (
<>
{ __(
'No analytics data yet',
'liana-with-growthstack'
) }
{ __(
'Analytics data will appear here once visitors start seeing this personalization on your published page.',
'liana-with-growthstack'
) }
>
) }
) }
{ ! isLoading &&
hasPersonalizations &&
personalizations.length > 0 && (
<>
{ personalizations.map( ( personalization ) => (
<>
{ personalization.name }
{ formatString(
// Translators: 1: personalization ID.
__(
'ID: %s',
'liana-with-growthstack'
),
personalization.id
) }
{ __(
'Variant',
'liana-with-growthstack'
) }
{ __(
'Show Rate',
'liana-with-growthstack'
) }
{ __(
'Views',
'liana-with-growthstack'
) }
{ __(
'Clicks',
'liana-with-growthstack'
) }
{ __(
'Clicks / Views',
'liana-with-growthstack'
) }
{ personalization.variants.map(
( variant ) => (
)
) }
{ showAdvanced[
personalization.id
] && (
{ __(
'Google Analytics',
'liana-with-growthstack'
) }
growthstack_personalization'
),
} }
>
' +
personalization.id +
''
),
} }
>
{ __(
'You can focus into different versions by variantId:',
'liana-with-growthstack'
) }
{ personalization.variants.map(
( variant ) => (
-
{ variant.name }
:{ ' ' }
{
variant.id
}
)
) }
) }
>
) ) }
>
) }
);
}