123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- import {Release} from '@sentry/release-parser';
- import round from 'lodash/round';
- import {t, tn} from 'sentry/locale';
- import {CommitAuthor, User} from 'sentry/types';
- import {RATE_UNIT_LABELS, RateUnits} from 'sentry/utils/discover/fields';
- export function userDisplayName(user: User | CommitAuthor, includeEmail = true): string {
- let displayName = String(user?.name ?? t('Unknown author')).trim();
- if (displayName.length <= 0) {
- displayName = t('Unknown author');
- }
- const email = String(user?.email ?? '').trim();
- if (email.length > 0 && email !== displayName && includeEmail) {
- displayName += ' (' + email + ')';
- }
- return displayName;
- }
- export const isSemverRelease = (rawVersion: string): boolean => {
- try {
- const parsedVersion = new Release(rawVersion);
- return !!parsedVersion.versionParsed;
- } catch {
- return false;
- }
- };
- export const formatVersion = (rawVersion: string, withPackage = false) => {
- try {
- const parsedVersion = new Release(rawVersion);
- const versionToDisplay = parsedVersion.describe();
- if (versionToDisplay.length) {
- return `${versionToDisplay}${
- withPackage && parsedVersion.package ? `, ${parsedVersion.package}` : ''
- }`;
- }
- return rawVersion;
- } catch {
- return rawVersion;
- }
- };
- function roundWithFixed(
- value: number,
- fixedDigits: number
- ): {label: string; result: number} {
- const label = value.toFixed(fixedDigits);
- const result = fixedDigits <= 0 ? Math.round(value) : value;
- return {label, result};
- }
- // in milliseconds
- export const MONTH = 2629800000;
- export const WEEK = 604800000;
- export const DAY = 86400000;
- export const HOUR = 3600000;
- export const MINUTE = 60000;
- export const SECOND = 1000;
- /**
- * Returns a human redable duration rounded to the largest unit.
- *
- * e.g. 2 days, or 3 months, or 25 seoconds
- *
- * Use `getExactDuration` for exact durations
- */
- export function getDuration(
- seconds: number,
- fixedDigits: number = 0,
- abbreviation: boolean = false,
- extraShort: boolean = false,
- absolute: boolean = false
- ): string {
- const absValue = Math.abs(seconds * 1000);
- // value in milliseconds
- const msValue = absolute ? absValue : seconds * 1000;
- if (absValue >= MONTH && !extraShort) {
- const {label, result} = roundWithFixed(msValue / MONTH, fixedDigits);
- return `${label}${abbreviation ? t('mo') : ` ${tn('month', 'months', result)}`}`;
- }
- if (absValue >= WEEK) {
- const {label, result} = roundWithFixed(msValue / WEEK, fixedDigits);
- if (extraShort) {
- return `${label}${t('w')}`;
- }
- if (abbreviation) {
- return `${label}${t('wk')}`;
- }
- return `${label} ${tn('week', 'weeks', result)}`;
- }
- if (absValue >= DAY) {
- const {label, result} = roundWithFixed(msValue / DAY, fixedDigits);
- if (extraShort || abbreviation) {
- return `${label}${t('d')}`;
- }
- return `${label} ${tn('day', 'days', result)}`;
- }
- if (absValue >= HOUR) {
- const {label, result} = roundWithFixed(msValue / HOUR, fixedDigits);
- if (extraShort) {
- return `${label}${t('h')}`;
- }
- if (abbreviation) {
- return `${label}${t('hr')}`;
- }
- return `${label} ${tn('hour', 'hours', result)}`;
- }
- if (absValue >= MINUTE) {
- const {label, result} = roundWithFixed(msValue / MINUTE, fixedDigits);
- if (extraShort) {
- return `${label}${t('m')}`;
- }
- if (abbreviation) {
- return `${label}${t('min')}`;
- }
- return `${label} ${tn('minute', 'minutes', result)}`;
- }
- if (absValue >= SECOND) {
- const {label, result} = roundWithFixed(msValue / SECOND, fixedDigits);
- if (extraShort || abbreviation) {
- return `${label}${t('s')}`;
- }
- return `${label} ${tn('second', 'seconds', result)}`;
- }
- const {label, result} = roundWithFixed(msValue, fixedDigits);
- if (extraShort || abbreviation) {
- return `${label}${t('ms')}`;
- }
- return `${label} ${tn('millisecond', 'milliseconds', result)}`;
- }
- /**
- * Returns a human readable exact duration.
- *
- * e.g. 1 hour 25 minutes 15 seconds
- */
- export function getExactDuration(seconds: number, abbreviation: boolean = false) {
- const convertDuration = (secs: number, abbr: boolean): string => {
- // value in milliseconds
- const msValue = round(secs * 1000);
- const value = round(Math.abs(secs * 1000));
- const divideBy = (time: number) => {
- return {
- quotient: msValue < 0 ? Math.ceil(msValue / time) : Math.floor(msValue / time),
- remainder: msValue % time,
- };
- };
- if (value >= WEEK) {
- const {quotient, remainder} = divideBy(WEEK);
- const suffix = abbr ? t('wk') : ` ${tn('week', 'weeks', quotient)}`;
- return `${quotient}${suffix} ${convertDuration(remainder / 1000, abbr)}`;
- }
- if (value >= DAY) {
- const {quotient, remainder} = divideBy(DAY);
- const suffix = abbr ? t('d') : ` ${tn('day', 'days', quotient)}`;
- return `${quotient}${suffix} ${convertDuration(remainder / 1000, abbr)}`;
- }
- if (value >= HOUR) {
- const {quotient, remainder} = divideBy(HOUR);
- const suffix = abbr ? t('hr') : ` ${tn('hour', 'hours', quotient)}`;
- return `${quotient}${suffix} ${convertDuration(remainder / 1000, abbr)}`;
- }
- if (value >= MINUTE) {
- const {quotient, remainder} = divideBy(MINUTE);
- const suffix = abbr ? t('min') : ` ${tn('minute', 'minutes', quotient)}`;
- return `${quotient}${suffix} ${convertDuration(remainder / 1000, abbr)}`;
- }
- if (value >= SECOND) {
- const {quotient, remainder} = divideBy(SECOND);
- const suffix = abbr ? t('s') : ` ${tn('second', 'seconds', quotient)}`;
- return `${quotient}${suffix} ${convertDuration(remainder / 1000, abbr)}`;
- }
- if (value === 0) {
- return '';
- }
- const suffix = abbr ? t('ms') : ` ${tn('millisecond', 'milliseconds', value)}`;
- return `${msValue}${suffix}`;
- };
- const result = convertDuration(seconds, abbreviation).trim();
- if (result.length) {
- return result;
- }
- return `0${abbreviation ? t('ms') : ` ${t('milliseconds')}`}`;
- }
- export function formatSecondsToClock(
- seconds: number,
- {padAll}: {padAll: boolean} = {padAll: true}
- ) {
- if (seconds === 0 || isNaN(seconds)) {
- return padAll ? '00:00' : '0:00';
- }
- const divideBy = (msValue: number, time: number) => {
- return {
- quotient: msValue < 0 ? Math.ceil(msValue / time) : Math.floor(msValue / time),
- remainder: msValue % time,
- };
- };
- // value in milliseconds
- const absMSValue = round(Math.abs(seconds * 1000));
- const {quotient: hours, remainder: rMins} = divideBy(absMSValue, HOUR);
- const {quotient: minutes, remainder: rSeconds} = divideBy(rMins, MINUTE);
- const {quotient: secs, remainder: milliseconds} = divideBy(rSeconds, SECOND);
- const fill = (num: number) => (num < 10 ? `0${num}` : String(num));
- const parts = hours
- ? [padAll ? fill(hours) : hours, fill(minutes), fill(secs)]
- : [padAll ? fill(minutes) : minutes, fill(secs)];
- const ms = `000${milliseconds}`.slice(-3);
- return milliseconds ? `${parts.join(':')}.${ms}` : parts.join(':');
- }
- export function parseClockToSeconds(clock: string) {
- const [rest, milliseconds] = clock.split('.');
- const parts = rest.split(':');
- let seconds = 0;
- const progression = [MONTH, WEEK, DAY, HOUR, MINUTE, SECOND].slice(parts.length * -1);
- for (let i = 0; i < parts.length; i++) {
- const num = Number(parts[i]) || 0;
- const time = progression[i] / 1000;
- seconds += num * time;
- }
- const ms = Number(milliseconds) || 0;
- return seconds + ms / 1000;
- }
- export function formatFloat(number: number, places: number) {
- const multi = Math.pow(10, places);
- return parseInt((number * multi).toString(), 10) / multi;
- }
- /**
- * Format a value between 0 and 1 as a percentage
- */
- export function formatPercentage(value: number, places: number = 2) {
- if (value === 0) {
- return '0%';
- }
- return (
- round(value * 100, places).toLocaleString(undefined, {
- maximumFractionDigits: places,
- }) + '%'
- );
- }
- const numberFormats = [
- [1000000000, 'b'],
- [1000000, 'm'],
- [1000, 'k'],
- ] as const;
- export function formatAbbreviatedNumber(number: number | string) {
- number = Number(number);
- let lookup: (typeof numberFormats)[number];
- // eslint-disable-next-line no-cond-assign
- for (let i = 0; (lookup = numberFormats[i]); i++) {
- const [suffixNum, suffix] = lookup;
- const shortValue = Math.floor(number / suffixNum);
- const fitsBound = number % suffixNum;
- if (shortValue <= 0) {
- continue;
- }
- return shortValue / 10 > 1 || !fitsBound
- ? `${shortValue}${suffix}`
- : `${formatFloat(number / suffixNum, 1)}${suffix}`;
- }
- return number.toLocaleString();
- }
- export function formatRate(value: number, unit: RateUnits = RateUnits.PER_SECOND) {
- return `${formatAbbreviatedNumber(value)}${RATE_UNIT_LABELS[unit]}`;
- }
|