123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- import {captureMessage} from '@sentry/react';
- import * as Sentry from '@sentry/react';
- import type {LegendComponentOption} from 'echarts';
- import {t} from 'sentry/locale';
- import type {Series} from 'sentry/types/echarts';
- import {defined, formatBytesBase2} from 'sentry/utils';
- import type {AggregationOutputType, RateUnit} from 'sentry/utils/discover/fields';
- import getDuration from 'sentry/utils/duration/getDuration';
- import {
- DAY,
- formatAbbreviatedNumber,
- formatPercentage,
- formatRate,
- HOUR,
- MINUTE,
- SECOND,
- WEEK,
- } from 'sentry/utils/formatters';
- /**
- * Formatter for chart tooltips that handle a variety of discover and metrics result values.
- * If the result is metric values, the value can be of type number or null
- */
- export function tooltipFormatter(
- value: number | null,
- outputType: AggregationOutputType = 'number'
- ): string {
- if (!defined(value)) {
- return '\u2014';
- }
- return tooltipFormatterUsingAggregateOutputType(value, outputType);
- }
- /**
- * Formatter for chart tooltips that takes the aggregate output type directly
- */
- export function tooltipFormatterUsingAggregateOutputType(
- value: number | null,
- type: string
- ): string {
- if (!defined(value)) {
- return '\u2014';
- }
- switch (type) {
- case 'integer':
- case 'number':
- return value.toLocaleString();
- case 'percentage':
- return formatPercentage(value, 2);
- case 'duration':
- return getDuration(value / 1000, 2, true);
- case 'size':
- return formatBytesBase2(value);
- default:
- return value.toString();
- }
- }
- /**
- * Formatter for chart axis labels that handle a variety of discover result values
- * This function is *very similar* to tooltipFormatter but outputs data with less precision.
- */
- export function axisLabelFormatter(
- value: number,
- outputType: AggregationOutputType,
- abbreviation: boolean = false,
- durationUnit?: number,
- rateUnit?: RateUnit
- ): string {
- return axisLabelFormatterUsingAggregateOutputType(
- value,
- outputType,
- abbreviation,
- durationUnit,
- rateUnit
- );
- }
- /**
- * Formatter for chart axis labels that takes the aggregate output type directly
- */
- export function axisLabelFormatterUsingAggregateOutputType(
- value: number,
- type: string,
- abbreviation: boolean = false,
- durationUnit?: number,
- rateUnit?: RateUnit
- ): string {
- switch (type) {
- case 'integer':
- case 'number':
- return abbreviation ? formatAbbreviatedNumber(value) : value.toLocaleString();
- case 'percentage':
- return formatPercentage(value, 0);
- case 'duration':
- return axisDuration(value, durationUnit);
- case 'size':
- return formatBytesBase2(value, 0);
- case 'rate':
- return formatRate(value, rateUnit);
- default:
- return value.toString();
- }
- }
- /**
- * Specialized duration formatting for axis labels.
- * In that context we are ok sacrificing accuracy for more
- * consistent sizing.
- *
- * @param value Number of milliseconds to format.
- */
- export function axisDuration(value: number, durationUnit?: number): string {
- durationUnit ??= categorizeDuration(value);
- if (value === 0) {
- return '0';
- }
- switch (durationUnit) {
- case WEEK: {
- const label = (value / WEEK).toFixed(0);
- return t('%swk', label);
- }
- case DAY: {
- const label = (value / DAY).toFixed(0);
- return t('%sd', label);
- }
- case HOUR: {
- const label = (value / HOUR).toFixed(0);
- return t('%shr', label);
- }
- case MINUTE: {
- const label = (value / MINUTE).toFixed(0);
- return t('%smin', label);
- }
- case SECOND: {
- const label = (value / SECOND).toFixed(0);
- return t('%ss', label);
- }
- default:
- const label = value.toFixed(0);
- return t('%sms', label);
- }
- }
- /**
- * Given an array of series and an eCharts legend object,
- * finds the range of y values (min and max) based on which series is selected in the legend.
- * Does not assume any ordering of series, will check min/max for all series in multiseries.
- * @param series Array of eCharts series
- * @param legend eCharts legend object
- * @returns
- */
- export function findRangeOfMultiSeries(series: Series[], legend?: LegendComponentOption) {
- const range: {max: number; min: number} = {
- max: 0,
- min: Infinity,
- };
- if (!series[0]?.data) {
- return undefined;
- }
- for (const {seriesName, data} of series) {
- if (legend?.selected?.[seriesName] !== false) {
- const max = Math.max(...data.map(({value}) => value).filter(Number.isFinite));
- const min = Math.min(...data.map(({value}) => value).filter(Number.isFinite));
- if (max > range.max) {
- range.max = max;
- }
- if (min < range.min) {
- range.min = min;
- }
- if (min < 0) {
- Sentry.withScope(scope => {
- scope.setTag('seriesName', seriesName);
- scope.setExtra('min', min);
- scope.setExtra('max', min);
- captureMessage('Found negative min value in multiseries');
- });
- }
- }
- }
- if (range.max === 0 && range.min === Infinity) {
- return undefined;
- }
- return range;
- }
- /**
- * Given a eCharts series and legend, returns the unit to be used on the yAxis for a duration chart
- * @param series eCharts series array
- * @param legend eCharts legend object
- * @returns
- */
- export function getDurationUnit(
- series: Series[],
- legend?: LegendComponentOption
- ): number {
- let durationUnit = 0;
- const range = findRangeOfMultiSeries(series, legend);
- if (range) {
- const avg = (range.max + range.min) / 2;
- durationUnit = categorizeDuration((range.max - range.min) / 5); // avg of 5 yAxis ticks per chart
- const numOfDigits = (avg / durationUnit).toFixed(0).length;
- if (numOfDigits > 6) {
- durationUnit = categorizeDuration(avg);
- }
- }
- return durationUnit;
- }
- /**
- * Categorizes the duration by Second, Minute, Hour, etc
- * Ex) categorizeDuration(1200) = MINUTE
- * @param value Duration in ms
- */
- export function categorizeDuration(value): number {
- if (value >= WEEK) {
- return WEEK;
- }
- if (value >= DAY) {
- return DAY;
- }
- if (value >= HOUR) {
- return HOUR;
- }
- if (value >= MINUTE) {
- return MINUTE;
- }
- if (value >= SECOND) {
- return SECOND;
- }
- return 1;
- }
|