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 ) => (
  1. { variant.name } :{ ' ' } { variant.id }
  2. ) ) }
) }
) ) } ) }
); }