Back

100% Statements 28/28
85.71% Branches 12/14
100% Functions 8/8
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                                    81x 4x                       4x 4x 4x 4x   4x               4x   7x 7x 7x   7x   7x 9x 9x           7x           7x 7x   4x 4x 4x   4x         3x                 4x           4x   4x              
import { store as coreStore, useEntityRecord } from '@wordpress/core-data';
import { useDispatch, useRegistry } from '@wordpress/data';
import { useCallback, useMemo, useRef } from 'react';
 
export type StatsSettings = {
	admin_bar: boolean;
	roles: string[];
	count_roles: string[];
	wpcom_reader_views_enabled: boolean;
};
 
type StatsOptions = Omit< StatsSettings, 'wpcom_reader_views_enabled' >;
 
type SiteSettings = {
	stats_options?: StatsOptions;
	wpcom_reader_views_enabled?: boolean;
};
 
const toSettings = ( site: SiteSettings | null | undefined ): StatsSettings | undefined =>
	site?.stats_options && {
		...site.stats_options,
		wpcom_reader_views_enabled: Boolean( site.wpcom_reader_views_enabled ),
	};
 
/**
 * The Stats settings, read from core's site settings entity and saved as each change is made.
 *
 * @return The settings, and the action that saves a change.
 */
export function useStatsSettings() {
	// The site settings entity has no ID.
	const site = useEntityRecord< SiteSettings >( 'root', 'site', undefined as unknown as string );
	const { editedRecord, edit } = site;
	const { saveEditedEntityRecord } = useDispatch( coreStore );
	const registry = useRegistry();
	// The change that last edited each setting; a failed save puts back only the settings it still owns.
	const owners = useRef( new Map< keyof StatsSettings, symbol >() );
 
	/**
	 * Save a change, or put back the stored values of what it changed when the site refuses it.
	 *
	 * @param values - The changed settings.
	 * @return Resolves once saved; rejects with the site's error.
	 */
	const saveChange = useCallback(
		async ( values: Partial< StatsSettings > ) => {
			const change = Symbol();
			( Object.keys( values ) as ( keyof StatsSettings )[] ).forEach( key =>
				owners.current.set( key, change )
			);
			const { wpcom_reader_views_enabled: readerViews, ...options } = values;
			// Read at call time: another change may have been edited or saved since the last render.
			const read = () => {
				const { getEntityRecord, getEditedEntityRecord } = registry.select( coreStore );
				return {
					stored: getEntityRecord( 'root', 'site' ) as SiteSettings | undefined,
					edited: getEditedEntityRecord( 'root', 'site' ) as SiteSettings,
				};
			};
 
			edit( {
				...( Object.keys( options ).length > 0 && {
					stats_options: { ...read().edited.stats_options, ...options },
				} ),
				...( readerViews !== undefined && { wpcom_reader_views_enabled: readerViews } ),
			} );
			try {
				await saveEditedEntityRecord( 'root', 'site', undefined, { throwOnError: true } );
			} catch ( error ) {
				const isOwned = ( key: keyof StatsSettings ) => owners.current.get( key ) === change;
				const optionKeys = ( Object.keys( options ) as ( keyof StatsOptions )[] ).filter( isOwned );
				const { stored, edited } = read();
				// Editing back to the stored values is how core-data drops an edit.
				edit( {
					...( optionKeys.length > 0 && {
						stats_options: {
							...edited.stats_options,
							...Object.fromEntries(
								optionKeys.map( key => [ key, stored?.stats_options?.[ key ] ] )
							),
						},
					} ),
					...( readerViews !== undefined &&
						isOwned( 'wpcom_reader_views_enabled' ) && {
							wpcom_reader_views_enabled: stored?.wpcom_reader_views_enabled,
						} ),
				} );
				throw error;
			}
		},
		[ edit, registry, saveEditedEntityRecord ]
	);
 
	const settings = useMemo( () => toSettings( editedRecord ), [ editedRecord ] );
 
	return {
		settings,
		// The route answers `null` for a stored value its schema refuses.
		isError: site.status === 'ERROR' || ( site.hasResolved && ! settings ),
		saveChange,
	};
}