Back

100% Statements 26/26
100% Branches 30/30
100% Functions 7/7
100% Lines 26/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127                                                        17x 26x                   4x                                                                                     17x 17x   17x       17x 17x 17x       17x 17x   17x 15x     17x 17x   17x   15x 45x 15x     30x 30x 4x 26x 8x   18x     30x          
/**
 * External dependencies
 */
import {
	useStatsEmailClicksBreakdown,
	type StatsEmailBreakdown,
} from '@jetpack-premium-analytics/data';
import { parseSiteDateTime } from '@jetpack-premium-analytics/datetime';
import { ButtonLink } from '@jetpack-premium-analytics/externals';
import { useMemo } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
/**
 * Internal dependencies
 */
import { isEmailSection } from './config';
import { useEmailTabScope } from './use-email-tab-scope';
import type { PostSummary } from './use-post-summary';
import type { SingleReportPageSection } from '../../registry';
import type { SectionState, SectionsContext } from '../adapter';
import type { PageEmptyStateProps } from '@jetpack-premium-analytics/widgets-toolkit';
 
/**
 * Whether a rate summary records any sends, opens or clicks.
 *
 * @param summary - A sanitized rate summary.
 * @return True when any count is positive.
 */
function hasEmailActivity( summary: StatsEmailBreakdown[ 'summary' ] | undefined ): boolean {
	return [ 'total_sends', 'total_opens', 'total_clicks' ].some(
		key => Number( summary?.[ key ] ?? 0 ) > 0
	);
}
 
/**
 * The state an email section shows in place of its header and widgets for a post never sent.
 *
 * @return The empty state.
 */
function notSentEmptyState(): PageEmptyStateProps {
	return {
		title: __( 'This post hasn’t been sent as a newsletter', 'jetpack-premium-analytics-pkg' ),
		description: __(
			'Newsletter can help you reach subscribers in their inbox.',
			'jetpack-premium-analytics-pkg'
		),
		actions: (
			<ButtonLink
				variant="outline"
				size="compact"
				href="https://jetpack.com/support/newsletter/"
				openInNewTab
			>
				{ __( 'Learn more', 'jetpack-premium-analytics-pkg' ) }
			</ButtonLink>
		),
	};
}
 
/**
 * The post page's sections. Kept in full because the email gating is not obvious from the code.
 *
 * Only a standard post can be sent as a newsletter, so the email sections hide once the post
 * type is known to be anything else. Until the type is known they show only when the URL already
 * names one, so a deep link never falls back to Post traffic. Their widgets report over the first
 * 30 days after the send (`useEmailTabScope`), handed over as a pinned window; until that window
 * is known they wait, unless the summary failed, in which case they mount and surface their own
 * errors. Once the clicks rate summary answers with no activity for a standard post, the never-sent
 * state replaces the header and the widgets.
 *
 * @param summary            - The post summary, in any state.
 * @param sections           - The record's sections.
 * @param context            - The entity, the section the URL names and the site timezone.
 * @param context.entityId   - The post id.
 * @param context.activeSlug - The section the URL names, if any.
 * @param context.timeZone   - The site timezone.
 * @return One state per section, in the record's order.
 */
export function usePostSections(
	summary: PostSummary,
	sections: SingleReportPageSection[],
	{ entityId, activeSlug, timeZone }: SectionsContext
): SectionState[] {
	const postId = entityId;
	const postType = summary.type;
	const showEmail =
		postId > 0 &&
		( postType === 'post' || ( postType === undefined && isEmailSection( activeSlug ) ) );
 
	// Not the opens summary: it nulls every field when sends went unrecorded (legacy sends).
	const gateEnabled = postId > 0 && ( postType === undefined || postType === 'post' );
	const gate = useStatsEmailClicksBreakdown( postId, 'rate', { enabled: gateEnabled } );
	const hasEmailStats = hasEmailActivity(
		( gate.data as StatsEmailBreakdown | undefined )?.summary
	);
	// Success, not `! isLoading`: a paused retry would otherwise flash the never-sent state.
	const isEmailNotSent = postType === 'post' && gate.isSuccess && ! hasEmailStats;
	const isEmailSendPending = gateEnabled && ! gate.isSuccess && ! gate.isError;
 
	const allTimeStart = useMemo(
		() => parseSiteDateTime( summary.allTimeStart ),
		[ summary.allTimeStart ]
	);
	const emailScope = useEmailTabScope( postId, allTimeStart, timeZone );
	const emailScopeBlocked = ! emailScope && ! summary.isLoading && summary.isError;
 
	return useMemo(
		() =>
			sections.map( section => {
				if ( ! isEmailSection( section.slug ) ) {
					return { section };
				}
 
				const state: SectionState = { section, hidden: ! showEmail };
				if ( isEmailNotSent ) {
					state.emptyState = notSentEmptyState();
				} else if ( isEmailSendPending || ! emailScope ) {
					state.scope = ! isEmailSendPending && emailScopeBlocked ? 'blocked' : 'pending';
				} else {
					state.scope = { reportParams: emailScope.reportParams, range: emailScope.range };
				}
 
				return state;
			} ),
		[ sections, showEmail, isEmailNotSent, isEmailSendPending, emailScope, emailScopeBlocked ]
	);
}