summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/lib
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-25 15:08:50 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-25 15:08:50 +0000
commite06d0e779673d745972863302858105aad9032e5 (patch)
tree0ff35b27a949a164f586613004b4abfe33e7d20e /app/assets/javascripts/lib
parentf7dae0cdcb70ecb71c1d65f099e9d96b27a4548c (diff)
downloadgitlab-ce-e06d0e779673d745972863302858105aad9032e5.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/lib')
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js10
-rw-r--r--app/assets/javascripts/lib/utils/highlight.js7
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js4
-rw-r--r--app/assets/javascripts/lib/utils/unit_format/formatter_factory.js119
-rw-r--r--app/assets/javascripts/lib/utils/unit_format/index.js167
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`);
+};