import type {DateTimeObject} from 'sentry/components/charts/utils';
import {getSeriesApiInterval} from 'sentry/components/charts/utils';
import {DATA_CATEGORY_INFO} from 'sentry/constants';
import type {DataCategoryInfo} from 'sentry/types/core';
import {formatBytesBase10} from 'sentry/utils/bytes/formatBytesBase10';
import {parsePeriodToHours} from 'sentry/utils/duration/parsePeriodToHours';
const MILLION = 10 ** 6;
const BILLION = 10 ** 9;
const GIGABYTE = 10 ** 9;
type FormatOptions = {
/**
* Truncate 1234 => 1.2k or 1,234,000 to 1.23M
*/
isAbbreviated?: boolean;
/**
* Convert attachments to use the most appropriate unit KB/MB/GB/TB/etc.
* Otherwise, it will default to GB
*/
useUnitScaling?: boolean;
};
/**
* This expects usage values/quantities for the data categories that we sell.
*
* Note: usageQuantity for Attachments should be in BYTES
*/
export function formatUsageWithUnits(
usageQuantity: number = 0,
dataCategory: DataCategoryInfo['plural'],
options: FormatOptions = {isAbbreviated: false, useUnitScaling: false}
): string {
if (dataCategory === DATA_CATEGORY_INFO.attachment.plural) {
if (options.useUnitScaling) {
return formatBytesBase10(usageQuantity);
}
const usageGb = usageQuantity / GIGABYTE;
return options.isAbbreviated
? `${abbreviateUsageNumber(usageGb)} GB`
: `${usageGb.toLocaleString(undefined, {maximumFractionDigits: 2})} GB`;
}
if (
dataCategory === DATA_CATEGORY_INFO.profileDuration.plural &&
Number.isFinite(usageQuantity)
) {
// Profile duration is in milliseconds, convert to hours
return (usageQuantity / 1000 / 60 / 60).toLocaleString(undefined, {
maximumFractionDigits: 2,
});
}
return options.isAbbreviated
? abbreviateUsageNumber(usageQuantity)
: usageQuantity.toLocaleString();
}
/**
* Good default for "formatUsageWithUnits"
*/
export function getFormatUsageOptions(
dataCategory: DataCategoryInfo['plural']
): FormatOptions {
return {
isAbbreviated: dataCategory !== DATA_CATEGORY_INFO.attachment.plural,
useUnitScaling: dataCategory === DATA_CATEGORY_INFO.attachment.plural,
};
}
/**
* Instead of using this function directly, use formatReservedWithUnits or
* formatUsageWithUnits with options.isAbbreviated to true instead.
*
* This function display different precision for billion/million/thousand to
* provide clarity on usage of errors/transactions/attachments to the user.
*
* If you are not displaying usage numbers, it might be better to use
* `formatAbbreviatedNumber` in 'sentry/utils/formatters'
*/
export function abbreviateUsageNumber(n: number) {
if (n >= BILLION) {
return (n / BILLION).toLocaleString(undefined, {maximumFractionDigits: 2}) + 'B';
}
if (n >= MILLION) {
return (n / MILLION).toLocaleString(undefined, {maximumFractionDigits: 1}) + 'M';
}
if (n >= 1000) {
return (n / 1000).toFixed().toLocaleString() + 'K';
}
// Do not show decimals
return n.toFixed().toLocaleString();
}
/**
* We want to display datetime in UTC in the following situations:
*
* 1) The user selected an absolute date range with UTC
* 2) The user selected a wide date range with 1d interval
*
* When the interval is 1d, we need to use UTC because the 24 hour range might
* shift forward/backward depending on the user's timezone, or it might be
* displayed as a day earlier/later
*/
export function isDisplayUtc(datetime: DateTimeObject): boolean {
if (datetime.utc) {
return true;
}
const interval = getSeriesApiInterval(datetime);
const hours = parsePeriodToHours(interval);
return hours >= 24;
}
/**
* HACK(dlee): client-side pagination
*/
export function getOffsetFromCursor(cursor?: string) {
const offset = Number(cursor?.split(':')[1]);
return isNaN(offset) ? 0 : offset;
}
/**
* HACK(dlee): client-side pagination
*/
export function getPaginationPageLink({
numRows,
pageSize,
offset,
}: {
numRows: number;
offset: number;
pageSize: number;
}) {
const prevOffset = offset - pageSize;
const nextOffset = offset + pageSize;
return `; rel="previous"; results="${prevOffset >= 0}"; cursor="0:${Math.max(
0,
prevOffset
)}:1", ; rel="next"; results="${
nextOffset < numRows
}"; cursor="0:${nextOffset}:0"`;
}