diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-25 18:08:50 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-25 18:08:50 +0300 |
commit | e06d0e779673d745972863302858105aad9032e5 (patch) | |
tree | 0ff35b27a949a164f586613004b4abfe33e7d20e /app/assets/javascripts/lib | |
parent | f7dae0cdcb70ecb71c1d65f099e9d96b27a4548c (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/lib')
5 files changed, 296 insertions, 11 deletions
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index ad8095e1ae3..a70bab013c6 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -1,5 +1,5 @@ import $ from 'jquery'; -import _ from 'underscore'; +import { isString, mapValues, isNumber, reduce } from 'lodash'; import * as timeago from 'timeago.js'; import dateFormat from 'dateformat'; import { languageCode, s__, __, n__ } from '../../locale'; @@ -79,7 +79,7 @@ export const getDayName = date => * @returns {String} */ export const formatDate = (datetime, format = 'mmm d, yyyy h:MMtt Z') => { - if (_.isString(datetime) && datetime.match(/\d+-\d+\d+ /)) { + if (isString(datetime) && datetime.match(/\d+-\d+\d+ /)) { throw new Error(__('Invalid date')); } return dateFormat(datetime, format); @@ -497,7 +497,7 @@ export const parseSeconds = ( let unorderedMinutes = Math.abs(seconds / SECONDS_PER_MINUTE); - return _.mapObject(timePeriodConstraints, minutesPerPeriod => { + return mapValues(timePeriodConstraints, minutesPerPeriod => { if (minutesPerPeriod === 0) { return 0; } @@ -516,7 +516,7 @@ export const parseSeconds = ( * If the 'fullNameFormat' param is passed it returns a non condensed string eg '1 week 3 days' */ export const stringifyTime = (timeObject, fullNameFormat = false) => { - const reducedTime = _.reduce( + const reducedTime = reduce( timeObject, (memo, unitValue, unitName) => { const isNonZero = Boolean(unitValue); @@ -642,7 +642,7 @@ export const dayAfter = date => new Date(newDate(date).setDate(date.getDate() + * @return {String} approximated time */ export const approximateDuration = (seconds = 0) => { - if (!_.isNumber(seconds) || seconds < 0) { + if (!isNumber(seconds) || seconds < 0) { return ''; } diff --git a/app/assets/javascripts/lib/utils/highlight.js b/app/assets/javascripts/lib/utils/highlight.js index 8f0afa3467d..b1dd562f63a 100644 --- a/app/assets/javascripts/lib/utils/highlight.js +++ b/app/assets/javascripts/lib/utils/highlight.js @@ -1,5 +1,4 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus'; -import _ from 'underscore'; import sanitize from 'sanitize-html'; /** @@ -17,11 +16,11 @@ import sanitize from 'sanitize-html'; * @param {String} matchSuffix The string to insert at the end of a match */ export default function highlight(string, match = '', matchPrefix = '<b>', matchSuffix = '</b>') { - if (_.isUndefined(string) || _.isNull(string)) { + if (!string) { return ''; } - if (_.isUndefined(match) || _.isNull(match) || match === '') { + if (!match) { return string; } @@ -34,7 +33,7 @@ export default function highlight(string, match = '', matchPrefix = '<b>', match return sanitizedValue .split('') .map((character, i) => { - if (_.contains(occurrences, i)) { + if (occurrences.includes(i)) { return `${matchPrefix}${character}${matchSuffix}`; } diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index a03fedcd7e7..9ed286826cc 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -1,4 +1,4 @@ -import _ from 'underscore'; +import { isString } from 'lodash'; /** * Adds a , to a string composed by numbers, at every 3 chars. @@ -199,7 +199,7 @@ export const splitCamelCase = string => * i.e. "My Group / My Subgroup / My Project" */ export const truncateNamespace = (string = '') => { - if (_.isNull(string) || !_.isString(string)) { + if (string === null || !isString(string)) { return ''; } diff --git a/app/assets/javascripts/lib/utils/unit_format/formatter_factory.js b/app/assets/javascripts/lib/utils/unit_format/formatter_factory.js new file mode 100644 index 00000000000..432a9254558 --- /dev/null +++ b/app/assets/javascripts/lib/utils/unit_format/formatter_factory.js @@ -0,0 +1,119 @@ +/** + * Formats a number as string using `toLocaleString`. + * + * @param {Number} number to be converted + * @param {params} Parameters + * @param {params.fractionDigits} Number of decimal digits + * to display, defaults to using `toLocaleString` defaults. + * @param {params.maxLength} Max output char lenght at the + * expense of precision, if the output is longer than this, + * the formatter switches to using exponential notation. + * @param {params.factor} Value is multiplied by this factor, + * useful for value normalization. + * @returns Formatted value + */ +function formatNumber( + value, + { fractionDigits = undefined, valueFactor = 1, style = undefined, maxLength = undefined }, +) { + if (value === null) { + return ''; + } + + const num = value * valueFactor; + const formatted = num.toLocaleString(undefined, { + minimumFractionDigits: fractionDigits, + maximumFractionDigits: fractionDigits, + style, + }); + + if (maxLength !== undefined && formatted.length > maxLength) { + // 123456 becomes 1.23e+8 + return num.toExponential(2); + } + return formatted; +} + +/** + * 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) => { + if (unitFactor === 0) { + return new RangeError(`unitFactor cannot have the value 0.`); + } + + return (value, fractionDigits) => { + if (value === null) { + return ''; + } + if ( + 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; + + if (scale >= limit) { + return 'NA'; + } + } + + const unit = units[scale]; + + return `${formatNumber(num, { fractionDigits })}${unit}`; + }; +}; + +/** + * Returns a function that formats a number as a string. + */ +export const numberFormatter = (style = 'decimal', valueFactor = 1) => { + return (value, fractionDigits, maxLength) => { + return `${formatNumber(value, { fractionDigits, maxLength, valueFactor, style })}`; + }; +}; + +/** + * Returns a function that formats a number as a string with a suffix. + */ +export const suffixFormatter = (unit = '', valueFactor = 1) => { + return (value, fractionDigits, maxLength) => { + const length = maxLength !== undefined ? maxLength - unit.length : undefined; + return `${formatNumber(value, { fractionDigits, maxLength: length, valueFactor })}${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/i18n/no-non-i18n-strings + throw new RangeError('The unit cannot be converted, please try a different scale'); + } + + return scaledFormatter(units); +}; diff --git a/app/assets/javascripts/lib/utils/unit_format/index.js b/app/assets/javascripts/lib/utils/unit_format/index.js new file mode 100644 index 00000000000..daf70ebb5d7 --- /dev/null +++ b/app/assets/javascripts/lib/utils/unit_format/index.js @@ -0,0 +1,167 @@ +import { s__ } from '~/locale'; + +import { suffixFormatter, scaledSIFormatter, numberFormatter } from './formatter_factory'; + +/** + * Supported formats + */ +export const SUPPORTED_FORMATS = { + // Number + number: 'number', + percent: 'percent', + percentHundred: 'percentHundred', + + // Duration + seconds: 'seconds', + miliseconds: 'miliseconds', + + // Digital + bytes: 'bytes', + kilobytes: 'kilobytes', + megabytes: 'megabytes', + gigabytes: 'gigabytes', + terabytes: 'terabytes', + petabytes: 'petabytes', +}; + +/** + * Returns a function that formats number to different units + * @param {String} format - Format to use, must be one of the SUPPORTED_FORMATS. Defaults to number. + * + * + */ +export const getFormatter = (format = SUPPORTED_FORMATS.number) => { + // Number + if (format === SUPPORTED_FORMATS.number) { + /** + * Formats a number + * + * @function + * @param {Number} value - Number to format + * @param {Number} fractionDigits - precision decimals + * @param {Number} maxLength - Max lenght of formatted number + * if lenght is exceeded, exponential format is used. + */ + return numberFormatter(); + } + if (format === SUPPORTED_FORMATS.percent) { + /** + * Formats a percentge (0 - 1) + * + * @function + * @param {Number} value - Number to format, `1` is rendered as `100%` + * @param {Number} fractionDigits - number of precision decimals + * @param {Number} maxLength - Max lenght of formatted number + * if lenght is exceeded, exponential format is used. + */ + return numberFormatter('percent'); + } + if (format === SUPPORTED_FORMATS.percentHundred) { + /** + * Formats a percentge (0 to 100) + * + * @function + * @param {Number} value - Number to format, `100` is rendered as `100%` + * @param {Number} fractionDigits - number of precision decimals + * @param {Number} maxLength - Max lenght of formatted number + * if lenght is exceeded, exponential format is used. + */ + return numberFormatter('percent', 1 / 100); + } + + // Durations + if (format === SUPPORTED_FORMATS.seconds) { + /** + * Formats a number of seconds + * + * @function + * @param {Number} value - Number to format, `1` is rendered as `1s` + * @param {Number} fractionDigits - number of precision decimals + * @param {Number} maxLength - Max lenght of formatted number + * if lenght is exceeded, exponential format is used. + */ + return suffixFormatter(s__('Units|s')); + } + if (format === SUPPORTED_FORMATS.miliseconds) { + /** + * Formats a number of miliseconds with ms as units + * + * @function + * @param {Number} value - Number to format, `1` is formatted as `1ms` + * @param {Number} fractionDigits - number of precision decimals + * @param {Number} maxLength - Max lenght of formatted number + * if lenght is exceeded, exponential format is used. + */ + return suffixFormatter(s__('Units|ms')); + } + + // Digital + if (format === SUPPORTED_FORMATS.bytes) { + /** + * Formats a number of bytes scaled up to larger digital + * units for larger numbers. + * + * @function + * @param {Number} value - Number to format, `1` is formatted as `1B` + * @param {Number} fractionDigits - number of precision decimals + */ + return scaledSIFormatter('B'); + } + if (format === SUPPORTED_FORMATS.kilobytes) { + /** + * Formats a number of kilobytes scaled up to larger digital + * units for larger numbers. + * + * @function + * @param {Number} value - Number to format, `1` is formatted as `1kB` + * @param {Number} fractionDigits - number of precision decimals + */ + return scaledSIFormatter('B', 1); + } + if (format === SUPPORTED_FORMATS.megabytes) { + /** + * Formats a number of megabytes scaled up to larger digital + * units for larger numbers. + * + * @function + * @param {Number} value - Number to format, `1` is formatted as `1MB` + * @param {Number} fractionDigits - number of precision decimals + */ + return scaledSIFormatter('B', 2); + } + if (format === SUPPORTED_FORMATS.gigabytes) { + /** + * Formats a number of gigabytes scaled up to larger digital + * units for larger numbers. + * + * @function + * @param {Number} value - Number to format, `1` is formatted as `1GB` + * @param {Number} fractionDigits - number of precision decimals + */ + return scaledSIFormatter('B', 3); + } + if (format === SUPPORTED_FORMATS.terabytes) { + /** + * Formats a number of terabytes scaled up to larger digital + * units for larger numbers. + * + * @function + * @param {Number} value - Number to format, `1` is formatted as `1GB` + * @param {Number} fractionDigits - number of precision decimals + */ + return scaledSIFormatter('B', 4); + } + if (format === SUPPORTED_FORMATS.petabytes) { + /** + * Formats a number of petabytes scaled up to larger digital + * units for larger numbers. + * + * @function + * @param {Number} value - Number to format, `1` is formatted as `1PB` + * @param {Number} fractionDigits - number of precision decimals + */ + return scaledSIFormatter('B', 5); + } + // Fail so client library addresses issue + throw TypeError(`${format} is not a valid number format`); +}; |