All files / api getSourcesData.js

95.74% Statements 45/47
82.6% Branches 38/46
92.85% Functions 13/14
95.12% Lines 39/41

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    1x 13x 13x 1x   12x 11x   1x             1x 2x 2x   2x 2x   2x                             1x                         1x 4x 1x       12x   4x   3x 4x   4x         1x                 1x 4x 1x       3x 3x 9x   9x 8x   9x     8x               8x 3x 1x     5x 2x   6x            
import { getData } from '@/utils/api';
 
const normalizeSourceLabel = ( rawSource ) => {
	let source = rawSource || 'Unknown';
	if ( 'direct' === source ) {
		return 'Direct / unknown';
	}
	if ( 'Direct / unknown' !== source && 'Email client' !== source ) {
		return source.charAt( 0 ).toUpperCase() + source.slice( 1 );
	}
	return source;
};
 
/**
 * Fetch time-bucketed traffic sources data.
 */
// fallow-ignore-next-line complexity
export const getSourcesOverTimeData = async({ startDate, endDate, range, args }) => {
	const response = await getData( 'sources-over-time', startDate, endDate, range, args );
	const finalData = response?.data || {};
 
	const timestamps = finalData.timestamps || [];
	const emptyArray = new Array( timestamps.length ).fill( 0 );
 
	return {
		timestamps,
		search: finalData.search || emptyArray,
		social: finalData.social || emptyArray,
		referral: finalData.referral || emptyArray,
		aiReferral: finalData.aiReferral || emptyArray,
		paid: finalData.paid || emptyArray,
		email: finalData.email || emptyArray,
		direct: finalData.direct || emptyArray
	};
};
 
/**
 * Fetch flat list of traffic sources from server.
 */
export const getSourcesListData = async({ startDate, endDate, range, args }) => {
	const response = await getData( 'sources-list', startDate, endDate, range, args );
	return response?.data || [];
};
 
/**
 * Get source breakdown for a category from the pre-fetched sourcesList.
 *
 * @param {Object} args            - Request arguments.
 * @param {Array}  args.sourcesList - The complete sources list from the server.
 * @param {string} args.category   - Selected traffic category.
 * @return {Array} Source rows for drill-down display.
 */
export const getSourcesDrilldownData = ({ sourcesList, category }) => {
	if ( ! Array.isArray( sourcesList ) ) {
		return [];
	}
 
 
	const filtered = sourcesList.filter( ( item ) => item.category === category );
 
	const total = filtered.reduce( ( sum, item ) => sum + parseInt( item.visitors || 0 ), 0 );
 
	return filtered.map( ( item ) => {
		const source = normalizeSourceLabel( item.source );
 
		return {
			source,
			visits: parseInt( item.visitors || 0 ),
			percentage: 0 < total ? ( parseInt( item.visitors || 0 ) / total ) * 100 : 0
		};
	}).sort( ( a, b ) => b.visits - a.visits );
};
 
/**
 * Get top traffic sources across all categories from the pre-fetched sourcesList.
 *
 * @param {Array} sourcesList - The complete sources list from the server.
 * @return {Array} Top source rows.
 */
export const getTopSourcesData = ( sourcesList ) => {
	if ( ! Array.isArray( sourcesList ) ) {
		return [];
	}
 
	// 1. Group/sum visitors by source name (in case a source appears in multiple categories)
	const sourceMap = {};
	sourcesList.forEach( ( item ) => {
		const source = normalizeSourceLabel( item.source );
 
		if ( ! sourceMap[ source ]) {
			sourceMap[ source ] = 0;
		}
		sourceMap[ source ] += parseInt( item.visitors || 0 );
	});
 
	const list = Object.keys( sourceMap ).map( ( source ) => ({
		id: source,
		source,
		visitors: sourceMap[ source ]
	}) );
 
	// 2. Sum all unique visitors in the list to calculate the true grandTotal
	// This ensures direct (and other unique sources) are divided by the correct denominator
	const grandTotal = list.reduce( ( sum, item ) => sum + item.visitors, 0 );
	if ( 0 === grandTotal ) {
		return [];
	}
 
	const sorted = list.sort( ( a, b ) => b.visitors - a.visitors );
	const top5 = sorted.slice( 0, 5 );
 
	return top5.map( ( item ) => ({
		id: item.id,
		source: item.source,
		percentage: ( item.visitors / grandTotal ) * 100
	}) );
};