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 | 2x 16x 2x 9x 1x 8x 8x 2x 3x 2x 1x 1x 2x 22x 9x 13x 3x 10x 8x 2x | import { formatPercentage } from './formatting';
export interface ChangeIndicator {
change: string | null;
changeStatus: string;
}
interface ResolveChangeArgs {
/** Server-calculated change percentage; null when a period has no data. */
rateChange?: number | null;
current?: Record<string, unknown> | null;
previous?: Record<string, unknown> | null;
/** Extracts the numeric metric value from a period's data object. */
getValue: ( data: Record<string, unknown> ) => number;
/** Set when growth is bad (e.g. abandonment rate) so statuses flip. */
invertPolarity?: boolean;
/** Returned when neither period has data. */
noData: ChangeIndicator;
}
const polarity = ( grew: boolean, invert: boolean ): string =>
grew !== invert ? 'positive' : 'negative';
const resolveFromRateChange = (
rateChange: number,
invert: boolean
): ChangeIndicator => {
if ( 0 === rateChange ) {
return { change: '0%', changeStatus: 'positive' };
}
const formatted = formatPercentage( rateChange );
return 0 < rateChange ?
{ change: `+${formatted}`, changeStatus: polarity( true, invert ) } :
{ change: formatted, changeStatus: polarity( false, invert ) };
};
const resolveFromPeriods = (
currentValue: number,
previousValue: number,
invert: boolean
): ChangeIndicator | null => {
if ( 0 === previousValue && 0 < currentValue ) {
// Positive infinity: went from 0 to something.
return { change: '∞', changeStatus: polarity( true, invert ) };
}
Eif ( 0 < previousValue && 0 === currentValue ) {
// Negative infinity: went from something to 0.
return { change: '-∞', changeStatus: polarity( false, invert ) };
}
if ( 0 === previousValue && 0 === currentValue ) {
return { change: '0%', changeStatus: 'positive' };
}
// Both periods have data but no rate change was provided.
return null;
};
/**
* Resolve the change label and status for a compared metric, handling the
* infinity cases (a period at zero) that a percentage cannot express.
*
* Returns null when both periods have non-zero data but no rateChange was
* provided; the caller keeps its initial values in that case.
*/
export const resolveChangeIndicator = ({
rateChange,
current,
previous,
getValue,
invertPolarity = false,
noData
}: ResolveChangeArgs ): ChangeIndicator | null => {
if ( null !== rateChange && undefined !== rateChange ) {
return resolveFromRateChange( rateChange, invertPolarity );
}
if ( current && previous ) {
return resolveFromPeriods(
getValue( current ),
getValue( previous ),
invertPolarity
);
}
if ( current ) {
// Only current data exists: treat any value as new data.
return 0 < getValue( current ) ?
{ change: '∞', changeStatus: polarity( true, invertPolarity ) } :
{ change: '0%', changeStatus: 'positive' };
}
return noData;
};
|