All files / app/assets/javascripts/lib/utils/unit_format formatter_factory.js

92.72% Statements 51/55
82.05% Branches 32/39
100% Functions 12/12
92.72% Lines 51/55

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 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173                                    44x 2451x   2451x   8x   2443x                         44x 2451x 401x   2050x                           44x 692x       692x 488x   488x     488x               488x 488x 488x   488x 351x 351x   351x         488x 488x   488x                     44x 182x 1524x   1524x                         44x 163x 439x   439x 439x                       44x 350x 350x 350x   350x 2287x     350x   1x     349x           44x   344x 344x   344x 2235x     344x   1x     343x    
import { formatNumber } from '~/locale';
 
/**
 * Formats a number as a string using `toLocaleString`.
 *
 * @param {Number} number to be converted
 *
 * @param {options.maxLength} Max output char length at the
 * expense of precision, if the output is longer than this,
 * the formatter switches to using exponential notation.
 *
 * @param {options.valueFactor} Value is multiplied by this factor,
 * useful for value normalization or to alter orders of magnitude.
 *
 * @param {options} Other options to be passed to
 * `formatNumber` such as `valueFactor`, `unit` and `style`.
 *
 */
const formatNumberNormalized = (value, { maxLength, valueFactor = 1, ...options }) => {
  const formatted = formatNumber(value * valueFactor, options);
 
  if (maxLength !== undefined && formatted.length > maxLength) {
    // 123456 becomes 1.23e+8
    return value.toExponential(2);
  }
  return formatted;
};
 
/**
 * This function converts the old positional arguments into an options
 * object.
 *
 * This is done so we can support legacy fractionDigits and maxLength as positional
 * arguments, as well as the better options object.
 *
 * @param {Object|Number} options
 * @returns {Object} options given to the formatter
 */
const getFormatterArguments = (options) => {
  if (typeof options === 'object' && options !== null) {
    return options;
  }
  return {
    maxLength: options,
  };
};
 
/**
 * Formats a number as a string scaling it up according to units.
 *
 * While the number is scaled down, the units are scaled up.
 *
 * @param {Array} List of units of the scale
 * @param {Number} unitFactor - Factor of the scale for each
 * unit after which the next unit is used scaled.
 */
const scaledFormatter = (units, unitFactor = 1000) => {
  Iif (unitFactor === 0) {
    return new RangeError(`unitFactor cannot have the value 0.`);
  }
 
  return (value, fractionDigits, options) => {
    const { maxLength, unitSeparator = '' } = getFormatterArguments(options);
 
    Iif (value === null) {
      return '';
    }
    Iif (
      value === Number.NEGATIVE_INFINITY ||
      value === Number.POSITIVE_INFINITY ||
      Number.isNaN(value)
    ) {
      return value.toLocaleString(undefined);
    }
 
    let num = value;
    let scale = 0;
    const limit = units.length;
 
    while (Math.abs(num) >= unitFactor) {
      scale += 1;
      num /= unitFactor;
 
      Iif (scale >= limit) {
        return 'NA';
      }
    }
 
    const unit = units[scale];
    const length = maxLength !== undefined ? maxLength - unit.length : undefined;
 
    return `${formatNumberNormalized(num, {
      maxLength: length,
      maximumFractionDigits: fractionDigits,
      minimumFractionDigits: fractionDigits,
    })}${unitSeparator}${unit}`;
  };
};
 
/**
 * Returns a function that formats a number as a string.
 */
export const numberFormatter = (style = 'decimal', valueFactor = 1) => {
  return (value, fractionDigits, options) => {
    const { maxLength } = getFormatterArguments(options);
 
    return formatNumberNormalized(value, {
      maxLength,
      valueFactor,
      style,
      maximumFractionDigits: fractionDigits,
      minimumFractionDigits: fractionDigits,
    });
  };
};
 
/**
 * Returns a function that formats a number as a string with a suffix.
 */
export const suffixFormatter = (unit = '', valueFactor = 1) => {
  return (value, fractionDigits, options) => {
    const { maxLength, unitSeparator = '' } = getFormatterArguments(options);
 
    const length = maxLength !== undefined ? maxLength - unit.length : undefined;
    return `${formatNumberNormalized(value, {
      maxLength: length,
      valueFactor,
      maximumFractionDigits: fractionDigits,
      minimumFractionDigits: fractionDigits,
    })}${unitSeparator}${unit}`;
  };
};
 
/**
 * Returns a function that formats a number scaled using SI units notation.
 */
export const scaledSIFormatter = (unit = '', prefixOffset = 0) => {
  const fractional = ['y', 'z', 'a', 'f', 'p', 'n', 'ยต', 'm'];
  const multiplicative = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
  const symbols = [...fractional, '', ...multiplicative];
 
  const units = symbols.slice(fractional.length + prefixOffset).map((prefix) => {
    return `${prefix}${unit}`;
  });
 
  if (!units.length) {
    // eslint-disable-next-line @gitlab/require-i18n-strings
    throw new RangeError('The unit cannot be converted, please try a different scale');
  }
 
  return scaledFormatter(units);
};
 
/**
 * Returns a function that formats a number scaled using SI units notation.
 */
export const scaledBinaryFormatter = (unit = '', prefixOffset = 0) => {
  // eslint-disable-next-line @gitlab/require-i18n-strings
  const multiplicative = ['Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'];
  const symbols = ['', ...multiplicative];
 
  const units = symbols.slice(prefixOffset).map((prefix) => {
    return `${prefix}${unit}`;
  });
 
  if (!units.length) {
    // eslint-disable-next-line @gitlab/require-i18n-strings
    throw new RangeError('The unit cannot be converted, please try a different scale');
  }
 
  return scaledFormatter(units, 1024);
};