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
}) );
};
|