Compare commits

...

29 Commits

Author SHA1 Message Date
Srikanth Chekuri
ce6381429e Merge branch 'main' into chore/metrics-explorer/y-axis-management 2025-07-19 11:56:43 +05:30
amlannandy
a44c7fcc20 chore: additional mapping for graph unit formatting 2025-07-08 12:41:27 +07:00
amlannandy
680161a4af chore: address comments 2025-07-08 12:41:27 +07:00
amlannandy
6ec55f60df chore: address comments 2025-07-08 12:41:27 +07:00
amlannandy
d71d5e917f chore: fix query parsing 2025-07-08 12:41:27 +07:00
amlannandy
cb82b020ec chore: prefill y axis unit in alerts 2025-07-08 12:41:27 +07:00
amlannandy
34eea2dd8a chore: remove console log 2025-07-08 12:41:27 +07:00
amlannandy
22118c7825 chore: update UTs 2025-07-08 12:41:27 +07:00
amlannandy
6c66409516 chore: fix conversion issues 2025-07-08 12:41:27 +07:00
amlannandy
e5907449ce chore: unit conversion changes 2025-07-08 12:41:27 +07:00
amlannandy
0aea80c60f chore: add tests 2025-07-08 12:41:27 +07:00
amlannandy
1d31ef81f3 chore: add tests 2025-07-08 12:41:27 +07:00
amlannandy
f14618fe10 chore: add new units to the converter 2025-07-08 12:41:27 +07:00
amlannandy
8bb7f7c058 chore: add new units to the formatter 2025-07-08 12:41:27 +07:00
Amlan Kumar Nandy
f831749575 chore: replace y axis unit selector in alerts and dashboards (#8167) 2025-07-08 12:41:27 +07:00
amlannandy
a86ee0f941 chore: handle units formatting where mapping is not foound 2025-07-08 12:41:27 +07:00
amlannandy
d4df241792 chore: update unit formatting mapping 2025-07-08 12:41:27 +07:00
amlannandy
c2d140be5c chore: fix tests 2025-07-08 12:41:27 +07:00
amlannandy
220b2d3a78 chore: update save button visibility 2025-07-08 12:41:27 +07:00
amlannandy
78920de08a chore: fix unit display 2025-07-08 12:41:27 +07:00
amlannandy
511ed22cfa chore: invalidate query key on update 2025-07-08 12:41:27 +07:00
amlannandy
599ea1bf2f chore: final changes 2025-07-08 12:41:27 +07:00
amlannandy
68c2dc3745 chore: update units file 2025-07-08 12:41:27 +07:00
amlannandy
b9c62a2d1a chore: update tooltips 2025-07-08 12:41:27 +07:00
amlannandy
68daf6a552 chore: address comments 2025-07-08 12:41:27 +07:00
amlannandy
d6ddc47a30 chore: add new y axis selector in explorer section 2025-07-08 12:41:27 +07:00
amlannandy
8474925d44 chore: add tests 2025-07-08 12:41:27 +07:00
amlannandy
18e4453b0d chore: enabled editing of unit in metric details 2025-07-08 12:41:27 +07:00
amlannandy
003d89e476 chore: create YAxisUnitSelector component 2025-07-08 12:41:27 +07:00
54 changed files with 2044 additions and 140 deletions

View File

@@ -9,6 +9,7 @@ export interface UpdateMetricMetadataProps {
metricType: MetricType;
temporality?: Temporality;
isMonotonic?: boolean;
unit?: string;
}
export interface UpdateMetricMetadataResponse {

View File

@@ -1,4 +1,9 @@
import { formattedValueToString, getValueFormat } from '@grafana/data';
import {
CustomGraphUnitToUniversalUnit,
UniversalUnitToGrafanaUnit,
} from 'components/YAxisUnitSelector/constants';
import { UniversalYAxisUnit } from 'components/YAxisUnitSelector/types';
export const getYAxisFormattedValue = (
value: string,
@@ -47,9 +52,34 @@ export const getYAxisFormattedValue = (
};
export const getToolTipValue = (value: string, format?: string): string => {
const universalMappingExists = format && format in UniversalUnitToGrafanaUnit;
const universalMappingNotFound =
format &&
format in UniversalYAxisUnit &&
!(format in UniversalUnitToGrafanaUnit);
let processedFormat = universalMappingExists
? UniversalUnitToGrafanaUnit[format as UniversalYAxisUnit]
: format;
// If using universal units but a compatible mapping is not found, use `short` for numeric formatting
if (universalMappingNotFound) {
processedFormat = 'short';
}
try {
return formattedValueToString(
getValueFormat(format)(parseFloat(value), undefined, undefined, undefined),
const valueFormat = getValueFormat(processedFormat)(
parseFloat(value),
undefined,
undefined,
undefined,
);
// For universal units, check if it requires a custom suffix
const suffix = valueFormat?.suffix?.trim() || '';
if (universalMappingExists && suffix in CustomGraphUnitToUniversalUnit) {
return `${valueFormat.text} ${CustomGraphUnitToUniversalUnit[suffix]}`;
}
return (
formattedValueToString(valueFormat) +
(universalMappingNotFound ? ` ${format}` : '')
);
} catch (error) {
console.error(error);

View File

@@ -0,0 +1,63 @@
import './styles.scss';
import { Select } from 'antd';
import { DefaultOptionType } from 'antd/es/select';
import { UniversalYAxisUnitMappings, Y_AXIS_CATEGORIES } from './constants';
import { UniversalYAxisUnit, YAxisUnitSelectorProps } from './types';
import { mapMetricUnitToUniversalUnit } from './utils';
function YAxisUnitSelector({
value,
onChange,
placeholder = 'Please select a unit',
loading = false,
}: YAxisUnitSelectorProps): JSX.Element {
const universalUnit = mapMetricUnitToUniversalUnit(value);
const handleSearch = (
searchTerm: string,
currentOption: DefaultOptionType | undefined,
): boolean => {
if (!currentOption?.value) return false;
const search = searchTerm.toLowerCase();
const unitId = currentOption.value.toString().toLowerCase();
const unitLabel = currentOption.children?.toString().toLowerCase() || '';
// Check label and id
if (unitId.includes(search) || unitLabel.includes(search)) return true;
// Check aliases (from the mapping) using array iteration
const aliases = Array.from(
UniversalYAxisUnitMappings[currentOption.value as UniversalYAxisUnit] ?? [],
);
return aliases.some((alias) => alias.toLowerCase().includes(search));
};
return (
<div className="y-axis-unit-selector-component">
<Select
showSearch
value={universalUnit}
onChange={onChange}
placeholder={placeholder}
filterOption={(input, option): boolean => handleSearch(input, option)}
loading={loading}
>
{Y_AXIS_CATEGORIES.map((category) => (
<Select.OptGroup key={category.name} label={category.name}>
{category.units.map((unit) => (
<Select.Option key={unit.id} value={unit.id}>
{unit.name}
</Select.Option>
))}
</Select.OptGroup>
))}
</Select>
</div>
);
}
export default YAxisUnitSelector;

View File

@@ -0,0 +1,68 @@
import { fireEvent, render, screen } from '@testing-library/react';
import YAxisUnitSelector from '../YAxisUnitSelector';
describe('YAxisUnitSelector', () => {
const mockOnChange = jest.fn();
beforeEach(() => {
mockOnChange.mockClear();
});
it('renders with default placeholder', () => {
render(<YAxisUnitSelector value="" onChange={mockOnChange} />);
expect(screen.getByText('Please select a unit')).toBeInTheDocument();
});
it('renders with custom placeholder', () => {
render(
<YAxisUnitSelector
value=""
onChange={mockOnChange}
placeholder="Custom placeholder"
/>,
);
expect(screen.queryByText('Custom placeholder')).toBeInTheDocument();
});
it('calls onChange when a value is selected', () => {
render(<YAxisUnitSelector value="" onChange={mockOnChange} />);
const select = screen.getByRole('combobox');
fireEvent.mouseDown(select);
const option = screen.getByText('Bytes (B)');
fireEvent.click(option);
expect(mockOnChange).toHaveBeenCalledWith('By', {
children: 'Bytes (B)',
key: 'By',
value: 'By',
});
});
it('filters options based on search input', () => {
render(<YAxisUnitSelector value="" onChange={mockOnChange} />);
const select = screen.getByRole('combobox');
fireEvent.mouseDown(select);
const input = screen.getByRole('combobox');
fireEvent.change(input, { target: { value: 'byte' } });
expect(screen.getByText('Bytes/sec')).toBeInTheDocument();
});
it('shows all categories and their units', () => {
render(<YAxisUnitSelector value="" onChange={mockOnChange} />);
const select = screen.getByRole('combobox');
fireEvent.mouseDown(select);
// Check for category headers
expect(screen.getByText('Data')).toBeInTheDocument();
expect(screen.getByText('Time')).toBeInTheDocument();
// Check for some common units
expect(screen.getByText('Bytes (B)')).toBeInTheDocument();
expect(screen.getByText('Seconds (s)')).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,39 @@
import {
getUniversalNameFromMetricUnit,
mapMetricUnitToUniversalUnit,
} from '../utils';
describe('YAxisUnitSelector utils', () => {
describe('mapMetricUnitToUniversalUnit', () => {
it('maps known units correctly', () => {
expect(mapMetricUnitToUniversalUnit('bytes')).toBe('By');
expect(mapMetricUnitToUniversalUnit('seconds')).toBe('s');
expect(mapMetricUnitToUniversalUnit('bytes_per_second')).toBe('By/s');
});
it('returns null or self for unknown units', () => {
expect(mapMetricUnitToUniversalUnit('unknown_unit')).toBe('unknown_unit');
expect(mapMetricUnitToUniversalUnit('')).toBe(null);
expect(mapMetricUnitToUniversalUnit(undefined)).toBe(null);
});
});
describe('getUniversalNameFromMetricUnit', () => {
it('returns human readable names for known units', () => {
expect(getUniversalNameFromMetricUnit('bytes')).toBe('Bytes (B)');
expect(getUniversalNameFromMetricUnit('seconds')).toBe('Seconds (s)');
expect(getUniversalNameFromMetricUnit('bytes_per_second')).toBe('Bytes/sec');
});
it('returns original unit for unknown units', () => {
expect(getUniversalNameFromMetricUnit('unknown_unit')).toBe('unknown_unit');
expect(getUniversalNameFromMetricUnit('')).toBe('-');
expect(getUniversalNameFromMetricUnit(undefined)).toBe('-');
});
it('handles case variations', () => {
expect(getUniversalNameFromMetricUnit('bytes')).toBe('Bytes (B)');
expect(getUniversalNameFromMetricUnit('s')).toBe('Seconds (s)');
});
});
});

View File

@@ -0,0 +1,549 @@
import { UniversalYAxisUnit, YAxisUnit } from './types';
export const UniversalYAxisUnitMappings: Record<
UniversalYAxisUnit,
Set<YAxisUnit>
> = {
[UniversalYAxisUnit.SECONDS]: new Set([
YAxisUnit.AWS_SECONDS,
YAxisUnit.UCUM_SECONDS,
YAxisUnit.OPEN_METRICS_SECONDS,
]),
[UniversalYAxisUnit.MICROSECONDS]: new Set([
YAxisUnit.AWS_MICROSECONDS,
YAxisUnit.UCUM_MICROSECONDS,
YAxisUnit.OPEN_METRICS_MICROSECONDS,
]),
[UniversalYAxisUnit.MILLISECONDS]: new Set([
YAxisUnit.AWS_MILLISECONDS,
YAxisUnit.UCUM_MILLISECONDS,
YAxisUnit.OPEN_METRICS_MILLISECONDS,
]),
[UniversalYAxisUnit.BYTES]: new Set([
YAxisUnit.AWS_BYTES,
YAxisUnit.UCUM_BYTES,
YAxisUnit.OPEN_METRICS_BYTES,
]),
[UniversalYAxisUnit.KILOBYTES]: new Set([
YAxisUnit.AWS_KILOBYTES,
YAxisUnit.UCUM_KILOBYTES,
YAxisUnit.OPEN_METRICS_KILOBYTES,
]),
[UniversalYAxisUnit.MEGABYTES]: new Set([
YAxisUnit.AWS_MEGABYTES,
YAxisUnit.UCUM_MEGABYTES,
YAxisUnit.OPEN_METRICS_MEGABYTES,
]),
[UniversalYAxisUnit.GIGABYTES]: new Set([
YAxisUnit.AWS_GIGABYTES,
YAxisUnit.UCUM_GIGABYTES,
YAxisUnit.OPEN_METRICS_GIGABYTES,
]),
[UniversalYAxisUnit.TERABYTES]: new Set([
YAxisUnit.AWS_TERABYTES,
YAxisUnit.UCUM_TERABYTES,
YAxisUnit.OPEN_METRICS_TERABYTES,
]),
[UniversalYAxisUnit.BYTES_SECOND]: new Set([
YAxisUnit.AWS_BYTES_SECOND,
YAxisUnit.UCUM_BYTES_SECOND,
YAxisUnit.OPEN_METRICS_BYTES_SECOND,
]),
[UniversalYAxisUnit.KILOBYTES_SECOND]: new Set([
YAxisUnit.AWS_KILOBYTES_SECOND,
YAxisUnit.UCUM_KILOBYTES_SECOND,
YAxisUnit.OPEN_METRICS_KILOBYTES_SECOND,
]),
[UniversalYAxisUnit.MEGABYTES_SECOND]: new Set([
YAxisUnit.AWS_MEGABYTES_SECOND,
YAxisUnit.UCUM_MEGABYTES_SECOND,
YAxisUnit.OPEN_METRICS_MEGABYTES_SECOND,
]),
[UniversalYAxisUnit.GIGABYTES_SECOND]: new Set([
YAxisUnit.AWS_GIGABYTES_SECOND,
YAxisUnit.UCUM_GIGABYTES_SECOND,
YAxisUnit.OPEN_METRICS_GIGABYTES_SECOND,
]),
[UniversalYAxisUnit.TERABYTES_SECOND]: new Set([
YAxisUnit.AWS_TERABYTES_SECOND,
YAxisUnit.UCUM_TERABYTES_SECOND,
YAxisUnit.OPEN_METRICS_TERABYTES_SECOND,
]),
[UniversalYAxisUnit.BITS]: new Set([
YAxisUnit.AWS_BITS,
YAxisUnit.UCUM_BITS,
YAxisUnit.OPEN_METRICS_BITS,
]),
[UniversalYAxisUnit.KILOBITS]: new Set([
YAxisUnit.AWS_KILOBITS,
YAxisUnit.UCUM_KILOBITS,
YAxisUnit.OPEN_METRICS_KILOBITS,
]),
[UniversalYAxisUnit.MEGABITS]: new Set([
YAxisUnit.AWS_MEGABITS,
YAxisUnit.UCUM_MEGABITS,
YAxisUnit.OPEN_METRICS_MEGABITS,
]),
[UniversalYAxisUnit.GIGABITS]: new Set([
YAxisUnit.AWS_GIGABITS,
YAxisUnit.UCUM_GIGABITS,
YAxisUnit.OPEN_METRICS_GIGABITS,
]),
[UniversalYAxisUnit.TERABITS]: new Set([
YAxisUnit.AWS_TERABITS,
YAxisUnit.UCUM_TERABITS,
YAxisUnit.OPEN_METRICS_TERABITS,
]),
[UniversalYAxisUnit.BITS_SECOND]: new Set([
YAxisUnit.AWS_BITS_SECOND,
YAxisUnit.UCUM_BITS_SECOND,
YAxisUnit.OPEN_METRICS_BITS_SECOND,
]),
[UniversalYAxisUnit.KILOBITS_SECOND]: new Set([
YAxisUnit.AWS_KILOBITS_SECOND,
YAxisUnit.UCUM_KILOBITS_SECOND,
YAxisUnit.OPEN_METRICS_KILOBITS_SECOND,
]),
[UniversalYAxisUnit.MEGABITS_SECOND]: new Set([
YAxisUnit.AWS_MEGABITS_SECOND,
YAxisUnit.UCUM_MEGABITS_SECOND,
YAxisUnit.OPEN_METRICS_MEGABITS_SECOND,
]),
[UniversalYAxisUnit.GIGABITS_SECOND]: new Set([
YAxisUnit.AWS_GIGABITS_SECOND,
YAxisUnit.UCUM_GIGABITS_SECOND,
YAxisUnit.OPEN_METRICS_GIGABITS_SECOND,
]),
[UniversalYAxisUnit.TERABITS_SECOND]: new Set([
YAxisUnit.AWS_TERABITS_SECOND,
YAxisUnit.UCUM_TERABITS_SECOND,
YAxisUnit.OPEN_METRICS_TERABITS_SECOND,
]),
[UniversalYAxisUnit.COUNT]: new Set([
YAxisUnit.AWS_COUNT,
YAxisUnit.UCUM_COUNT,
YAxisUnit.OPEN_METRICS_COUNT,
]),
[UniversalYAxisUnit.COUNT_SECOND]: new Set([
YAxisUnit.AWS_COUNT_SECOND,
YAxisUnit.UCUM_COUNT_SECOND,
YAxisUnit.OPEN_METRICS_COUNT_SECOND,
]),
[UniversalYAxisUnit.PERCENT]: new Set([
YAxisUnit.AWS_PERCENT,
YAxisUnit.UCUM_PERCENT,
YAxisUnit.OPEN_METRICS_PERCENT,
]),
[UniversalYAxisUnit.NONE]: new Set([
YAxisUnit.AWS_NONE,
YAxisUnit.UCUM_NONE,
YAxisUnit.OPEN_METRICS_NONE,
]),
[UniversalYAxisUnit.DAYS]: new Set([
YAxisUnit.UCUM_DAYS,
YAxisUnit.OPEN_METRICS_DAYS,
]),
[UniversalYAxisUnit.HOURS]: new Set([
YAxisUnit.UCUM_HOURS,
YAxisUnit.OPEN_METRICS_HOURS,
]),
[UniversalYAxisUnit.MINUTES]: new Set([
YAxisUnit.UCUM_MINUTES,
YAxisUnit.OPEN_METRICS_MINUTES,
]),
[UniversalYAxisUnit.NANOSECONDS]: new Set([
YAxisUnit.UCUM_NANOSECONDS,
YAxisUnit.OPEN_METRICS_NANOSECONDS,
]),
[UniversalYAxisUnit.PETABYTES]: new Set([
YAxisUnit.UCUM_PEBIBYTES,
YAxisUnit.OPEN_METRICS_PEBIBYTES,
]),
[UniversalYAxisUnit.PETABYTES_SECOND]: new Set([
YAxisUnit.UCUM_PEBIBYTES_SECOND,
YAxisUnit.OPEN_METRICS_PEBIBYTES_SECOND,
]),
[UniversalYAxisUnit.PETABITS]: new Set([YAxisUnit.UCUM_PETABITS]),
[UniversalYAxisUnit.PETABITS_SECOND]: new Set([
YAxisUnit.UCUM_PEBIBITS_SECOND,
YAxisUnit.OPEN_METRICS_PEBIBITS_SECOND,
]),
[UniversalYAxisUnit.COUNT_MINUTE]: new Set([
YAxisUnit.UCUM_COUNTS_MINUTE,
YAxisUnit.OPEN_METRICS_COUNTS_MINUTE,
]),
[UniversalYAxisUnit.OPS_SECOND]: new Set([
YAxisUnit.UCUM_OPS_SECOND,
YAxisUnit.OPEN_METRICS_OPS_SECOND,
]),
[UniversalYAxisUnit.OPS_MINUTE]: new Set([
YAxisUnit.UCUM_OPS_MINUTE,
YAxisUnit.OPEN_METRICS_OPS_MINUTE,
]),
[UniversalYAxisUnit.REQUESTS_SECOND]: new Set([
YAxisUnit.UCUM_REQUESTS_SECOND,
YAxisUnit.OPEN_METRICS_REQUESTS_SECOND,
]),
[UniversalYAxisUnit.READS_SECOND]: new Set([
YAxisUnit.UCUM_READS_SECOND,
YAxisUnit.OPEN_METRICS_READS_SECOND,
]),
[UniversalYAxisUnit.WRITES_SECOND]: new Set([
YAxisUnit.UCUM_WRITES_SECOND,
YAxisUnit.OPEN_METRICS_WRITES_SECOND,
]),
[UniversalYAxisUnit.READS_MINUTE]: new Set([
YAxisUnit.UCUM_READS_MINUTE,
YAxisUnit.OPEN_METRICS_READS_MINUTE,
]),
[UniversalYAxisUnit.WRITES_MINUTE]: new Set([
YAxisUnit.UCUM_WRITES_MINUTE,
YAxisUnit.OPEN_METRICS_WRITES_MINUTE,
]),
[UniversalYAxisUnit.IOOPS_SECOND]: new Set([
YAxisUnit.UCUM_IOPS_SECOND,
YAxisUnit.OPEN_METRICS_IOPS_SECOND,
]),
[UniversalYAxisUnit.PERCENT_UNIT]: new Set([
YAxisUnit.OPEN_METRICS_PERCENT_UNIT,
]),
};
export const Y_AXIS_UNIT_NAMES: Record<UniversalYAxisUnit, string> = {
[UniversalYAxisUnit.SECONDS]: 'Seconds (s)',
[UniversalYAxisUnit.MILLISECONDS]: 'Milliseconds (ms)',
[UniversalYAxisUnit.MICROSECONDS]: 'Microseconds (µs)',
[UniversalYAxisUnit.BYTES]: 'Bytes (B)',
[UniversalYAxisUnit.KILOBYTES]: 'Kilobytes (KB)',
[UniversalYAxisUnit.MEGABYTES]: 'Megabytes (MB)',
[UniversalYAxisUnit.GIGABYTES]: 'Gigabytes (GB)',
[UniversalYAxisUnit.TERABYTES]: 'Terabytes (TB)',
[UniversalYAxisUnit.PETABYTES]: 'Petabytes (PB)',
[UniversalYAxisUnit.BITS]: 'Bits (b)',
[UniversalYAxisUnit.KILOBITS]: 'Kilobits (Kb)',
[UniversalYAxisUnit.MEGABITS]: 'Megabits (Mb)',
[UniversalYAxisUnit.GIGABITS]: 'Gigabits (Gb)',
[UniversalYAxisUnit.TERABITS]: 'Terabits (Tb)',
[UniversalYAxisUnit.PETABITS]: 'Petabits (Pb)',
[UniversalYAxisUnit.BYTES_SECOND]: 'Bytes/sec',
[UniversalYAxisUnit.KILOBYTES_SECOND]: 'Kilobytes/sec',
[UniversalYAxisUnit.MEGABYTES_SECOND]: 'Megabytes/sec',
[UniversalYAxisUnit.GIGABYTES_SECOND]: 'Gigabytes/sec',
[UniversalYAxisUnit.TERABYTES_SECOND]: 'Terabytes/sec',
[UniversalYAxisUnit.PETABYTES_SECOND]: 'Petabytes/sec',
[UniversalYAxisUnit.BITS_SECOND]: 'Bits/sec',
[UniversalYAxisUnit.KILOBITS_SECOND]: 'Kilobits/sec',
[UniversalYAxisUnit.MEGABITS_SECOND]: 'Megabits/sec',
[UniversalYAxisUnit.GIGABITS_SECOND]: 'Gigabits/sec',
[UniversalYAxisUnit.TERABITS_SECOND]: 'Terabits/sec',
[UniversalYAxisUnit.PETABITS_SECOND]: 'Petabits/sec',
[UniversalYAxisUnit.COUNT]: 'Count',
[UniversalYAxisUnit.COUNT_SECOND]: 'Count/sec',
[UniversalYAxisUnit.PERCENT]: 'Percent (0 - 100)',
[UniversalYAxisUnit.NONE]: 'None',
[UniversalYAxisUnit.DAYS]: 'Days',
[UniversalYAxisUnit.HOURS]: 'Hours',
[UniversalYAxisUnit.MINUTES]: 'Minutes',
[UniversalYAxisUnit.NANOSECONDS]: 'Nanoseconds',
[UniversalYAxisUnit.COUNT_MINUTE]: 'Count/min',
[UniversalYAxisUnit.OPS_SECOND]: 'Ops/sec',
[UniversalYAxisUnit.OPS_MINUTE]: 'Ops/min',
[UniversalYAxisUnit.REQUESTS_SECOND]: 'Requests/sec',
[UniversalYAxisUnit.READS_SECOND]: 'Reads/sec',
[UniversalYAxisUnit.WRITES_SECOND]: 'Writes/sec',
[UniversalYAxisUnit.READS_MINUTE]: 'Reads/min',
[UniversalYAxisUnit.WRITES_MINUTE]: 'Writes/min',
[UniversalYAxisUnit.IOOPS_SECOND]: 'IOPS/sec',
[UniversalYAxisUnit.PERCENT_UNIT]: 'Percent (0.0 - 1.0)',
};
export const Y_AXIS_CATEGORIES = [
{
name: 'Time ',
units: [
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.SECONDS],
id: UniversalYAxisUnit.SECONDS,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MILLISECONDS],
id: UniversalYAxisUnit.MILLISECONDS,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MICROSECONDS],
id: UniversalYAxisUnit.MICROSECONDS,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.NANOSECONDS],
id: UniversalYAxisUnit.NANOSECONDS,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MINUTES],
id: UniversalYAxisUnit.MINUTES,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.HOURS],
id: UniversalYAxisUnit.HOURS,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.DAYS],
id: UniversalYAxisUnit.DAYS,
},
],
},
{
name: 'Data ',
units: [
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.BYTES],
id: UniversalYAxisUnit.BYTES,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.KILOBYTES],
id: UniversalYAxisUnit.KILOBYTES,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MEGABYTES],
id: UniversalYAxisUnit.MEGABYTES,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.GIGABYTES],
id: UniversalYAxisUnit.GIGABYTES,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.TERABYTES],
id: UniversalYAxisUnit.TERABYTES,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PETABYTES],
id: UniversalYAxisUnit.PETABYTES,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.BITS],
id: UniversalYAxisUnit.BITS,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.KILOBITS],
id: UniversalYAxisUnit.KILOBITS,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MEGABITS],
id: UniversalYAxisUnit.MEGABITS,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.GIGABITS],
id: UniversalYAxisUnit.GIGABITS,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.TERABITS],
id: UniversalYAxisUnit.TERABITS,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PETABITS],
id: UniversalYAxisUnit.PETABITS,
},
],
},
{
name: 'Data Rate',
units: [
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.BYTES_SECOND],
id: UniversalYAxisUnit.BYTES_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.KILOBYTES_SECOND],
id: UniversalYAxisUnit.KILOBYTES_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MEGABYTES_SECOND],
id: UniversalYAxisUnit.MEGABYTES_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.GIGABYTES_SECOND],
id: UniversalYAxisUnit.GIGABYTES_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.TERABYTES_SECOND],
id: UniversalYAxisUnit.TERABYTES_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PETABYTES_SECOND],
id: UniversalYAxisUnit.PETABYTES_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.BITS_SECOND],
id: UniversalYAxisUnit.BITS_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.KILOBITS_SECOND],
id: UniversalYAxisUnit.KILOBITS_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MEGABITS_SECOND],
id: UniversalYAxisUnit.MEGABITS_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.GIGABITS_SECOND],
id: UniversalYAxisUnit.GIGABITS_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.TERABITS_SECOND],
id: UniversalYAxisUnit.TERABITS_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PETABITS_SECOND],
id: UniversalYAxisUnit.PETABITS_SECOND,
},
],
},
{
name: 'Count',
units: [
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.COUNT],
id: UniversalYAxisUnit.COUNT,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.COUNT_SECOND],
id: UniversalYAxisUnit.COUNT_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.COUNT_MINUTE],
id: UniversalYAxisUnit.COUNT_MINUTE,
},
],
},
{
name: 'Operations',
units: [
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.OPS_SECOND],
id: UniversalYAxisUnit.OPS_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.OPS_MINUTE],
id: UniversalYAxisUnit.OPS_MINUTE,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.REQUESTS_SECOND],
id: UniversalYAxisUnit.REQUESTS_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.READS_SECOND],
id: UniversalYAxisUnit.READS_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.WRITES_SECOND],
id: UniversalYAxisUnit.WRITES_SECOND,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.READS_MINUTE],
id: UniversalYAxisUnit.READS_MINUTE,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.WRITES_MINUTE],
id: UniversalYAxisUnit.WRITES_MINUTE,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.IOOPS_SECOND],
id: UniversalYAxisUnit.IOOPS_SECOND,
},
],
},
{
name: 'Percentage',
units: [
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PERCENT],
id: UniversalYAxisUnit.PERCENT,
},
{
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PERCENT_UNIT],
id: UniversalYAxisUnit.PERCENT_UNIT,
},
],
},
];
export const UniversalUnitToGrafanaUnit: Record<UniversalYAxisUnit, string> = {
// Time
[UniversalYAxisUnit.DAYS]: 'd',
[UniversalYAxisUnit.HOURS]: 'h',
[UniversalYAxisUnit.MINUTES]: 'm',
[UniversalYAxisUnit.SECONDS]: 's',
[UniversalYAxisUnit.MILLISECONDS]: 'ms',
[UniversalYAxisUnit.MICROSECONDS]: 'µs',
[UniversalYAxisUnit.NANOSECONDS]: 'ns',
// Data (Grafana uses 1024-based IEC format)
[UniversalYAxisUnit.BYTES]: 'decbytes',
[UniversalYAxisUnit.KILOBYTES]: 'deckbytes',
[UniversalYAxisUnit.MEGABYTES]: 'decmbytes',
[UniversalYAxisUnit.GIGABYTES]: 'decgbytes',
[UniversalYAxisUnit.TERABYTES]: 'dectbytes',
[UniversalYAxisUnit.PETABYTES]: 'decpbytes',
// Data Rate
[UniversalYAxisUnit.BYTES_SECOND]: 'Bps',
[UniversalYAxisUnit.KILOBYTES_SECOND]: 'KBs',
[UniversalYAxisUnit.MEGABYTES_SECOND]: 'MBs',
[UniversalYAxisUnit.GIGABYTES_SECOND]: 'GBs',
[UniversalYAxisUnit.TERABYTES_SECOND]: 'TBs',
[UniversalYAxisUnit.PETABYTES_SECOND]: 'PBs',
// Bits
[UniversalYAxisUnit.BITS]: 'bits',
[UniversalYAxisUnit.KILOBITS]: 'kbytes',
[UniversalYAxisUnit.MEGABITS]: 'mbytes',
[UniversalYAxisUnit.GIGABITS]: 'gbytes',
[UniversalYAxisUnit.TERABITS]: 'tbytes',
[UniversalYAxisUnit.PETABITS]: 'pbytes',
// Bit Rate
[UniversalYAxisUnit.BITS_SECOND]: 'bps',
[UniversalYAxisUnit.KILOBITS_SECOND]: 'Kbits',
[UniversalYAxisUnit.MEGABITS_SECOND]: 'Mbits',
[UniversalYAxisUnit.GIGABITS_SECOND]: 'Gbits',
[UniversalYAxisUnit.TERABITS_SECOND]: 'Tbits',
[UniversalYAxisUnit.PETABITS_SECOND]: 'Pbits',
// Count
[UniversalYAxisUnit.COUNT]: 'short',
[UniversalYAxisUnit.COUNT_SECOND]: 'cps',
[UniversalYAxisUnit.COUNT_MINUTE]: 'cpm',
// Operations
[UniversalYAxisUnit.OPS_SECOND]: 'ops',
[UniversalYAxisUnit.OPS_MINUTE]: 'opm',
// Requests
[UniversalYAxisUnit.REQUESTS_SECOND]: 'reqps',
// Reads/Writes
[UniversalYAxisUnit.READS_SECOND]: 'rps',
[UniversalYAxisUnit.WRITES_SECOND]: 'wps',
[UniversalYAxisUnit.READS_MINUTE]: 'rpm',
[UniversalYAxisUnit.WRITES_MINUTE]: 'wpm',
// IO Operations
[UniversalYAxisUnit.IOOPS_SECOND]: 'iops',
// Percent
[UniversalYAxisUnit.PERCENT]: 'percent',
[UniversalYAxisUnit.PERCENT_UNIT]: 'percentunit',
// None
[UniversalYAxisUnit.NONE]: 'none',
};
export const CustomGraphUnitToUniversalUnit: Record<
string,
UniversalYAxisUnit
> = {
KiB: UniversalYAxisUnit.KILOBITS,
MiB: UniversalYAxisUnit.MEGABITS,
GiB: UniversalYAxisUnit.GIGABITS,
TiB: UniversalYAxisUnit.TERABITS,
PiB: UniversalYAxisUnit.PETABITS,
};

View File

@@ -0,0 +1,3 @@
import YAxisUnitSelector from './YAxisUnitSelector';
export default YAxisUnitSelector;

View File

@@ -0,0 +1,5 @@
.y-axis-unit-selector-component {
.ant-select {
width: 220px;
}
}

View File

@@ -0,0 +1,286 @@
export interface YAxisUnitSelectorProps {
value: string | undefined;
onChange: (value: UniversalYAxisUnit) => void;
placeholder?: string;
loading?: boolean;
disabled?: boolean;
}
export enum UniversalYAxisUnit {
// Time
DAYS = 'd',
HOURS = 'h',
MINUTES = 'min',
SECONDS = 's',
MICROSECONDS = 'us',
MILLISECONDS = 'ms',
NANOSECONDS = 'ns',
// Data
BYTES = 'By',
KILOBYTES = 'kBy',
MEGABYTES = 'MBy',
GIGABYTES = 'GBy',
TERABYTES = 'TBy',
PETABYTES = 'PBy',
// Data Rate
BYTES_SECOND = 'By/s',
KILOBYTES_SECOND = 'kBy/s',
MEGABYTES_SECOND = 'MBy/s',
GIGABYTES_SECOND = 'GBy/s',
TERABYTES_SECOND = 'TBy/s',
PETABYTES_SECOND = 'PBy/s',
// Bits
BITS = 'bit',
KILOBITS = 'kbit',
MEGABITS = 'Mbit',
GIGABITS = 'Gbit',
TERABITS = 'Tbit',
PETABITS = 'Pbit',
// Bit Rate
BITS_SECOND = 'bit/s',
KILOBITS_SECOND = 'kbit/s',
MEGABITS_SECOND = 'Mbit/s',
GIGABITS_SECOND = 'Gbit/s',
TERABITS_SECOND = 'Tbit/s',
PETABITS_SECOND = 'Pbit/s',
// Count
COUNT = '{count}',
COUNT_SECOND = '{count}/s',
COUNT_MINUTE = '{count}/min',
// Operations
OPS_SECOND = '{ops}/s',
OPS_MINUTE = '{ops}/min',
// Requests
REQUESTS_SECOND = '{req}/s',
// Reads/Writes
READS_SECOND = '{read}/s',
WRITES_SECOND = '{write}/s',
READS_MINUTE = '{read}/min',
WRITES_MINUTE = '{write}/min',
// IO Operations
IOOPS_SECOND = '{iops}/s',
// Percent
PERCENT = '%',
PERCENT_UNIT = 'percentunit',
NONE = '1',
}
export enum YAxisUnit {
AWS_SECONDS = 'Seconds',
UCUM_SECONDS = 's',
OPEN_METRICS_SECONDS = 'seconds',
AWS_MICROSECONDS = 'Microseconds',
UCUM_MICROSECONDS = 'us',
OPEN_METRICS_MICROSECONDS = 'microseconds',
AWS_MILLISECONDS = 'Milliseconds',
UCUM_MILLISECONDS = 'ms',
OPEN_METRICS_MILLISECONDS = 'milliseconds',
AWS_BYTES = 'Bytes',
UCUM_BYTES = 'By',
OPEN_METRICS_BYTES = 'bytes',
AWS_KILOBYTES = 'Kilobytes',
UCUM_KILOBYTES = 'kBy',
OPEN_METRICS_KILOBYTES = 'kilobytes',
AWS_MEGABYTES = 'Megabytes',
UCUM_MEGABYTES = 'MBy',
OPEN_METRICS_MEGABYTES = 'megabytes',
AWS_GIGABYTES = 'Gigabytes',
UCUM_GIGABYTES = 'GBy',
OPEN_METRICS_GIGABYTES = 'gigabytes',
AWS_TERABYTES = 'Terabytes',
UCUM_TERABYTES = 'TBy',
OPEN_METRICS_TERABYTES = 'terabytes',
AWS_BYTES_SECOND = 'Bytes/Second',
UCUM_BYTES_SECOND = 'By/s',
OPEN_METRICS_BYTES_SECOND = 'bytes_per_second',
AWS_KILOBYTES_SECOND = 'Kilobytes/Second',
UCUM_KILOBYTES_SECOND = 'kBy/s',
OPEN_METRICS_KILOBYTES_SECOND = 'kilobytes_per_second',
AWS_MEGABYTES_SECOND = 'Megabytes/Second',
UCUM_MEGABYTES_SECOND = 'MBy/s',
OPEN_METRICS_MEGABYTES_SECOND = 'megabytes_per_second',
AWS_GIGABYTES_SECOND = 'Gigabytes/Second',
UCUM_GIGABYTES_SECOND = 'GBy/s',
OPEN_METRICS_GIGABYTES_SECOND = 'gigabytes_per_second',
AWS_TERABYTES_SECOND = 'Terabytes/Second',
UCUM_TERABYTES_SECOND = 'TBy/s',
OPEN_METRICS_TERABYTES_SECOND = 'terabytes_per_second',
AWS_BITS = 'Bits',
UCUM_BITS = 'bit',
OPEN_METRICS_BITS = 'bits',
AWS_KILOBITS = 'Kilobits',
UCUM_KILOBITS = 'kbit',
OPEN_METRICS_KILOBITS = 'kilobits',
AWS_MEGABITS = 'Megabits',
UCUM_MEGABITS = 'Mbit',
OPEN_METRICS_MEGABITS = 'megabits',
AWS_GIGABITS = 'Gigabits',
UCUM_GIGABITS = 'Gbit',
OPEN_METRICS_GIGABITS = 'gigabits',
AWS_TERABITS = 'Terabits',
UCUM_TERABITS = 'Tbit',
OPEN_METRICS_TERABITS = 'terabits',
UCUM_PETABITS = 'Pbit',
AWS_BITS_SECOND = 'Bits/Second',
UCUM_BITS_SECOND = 'bit/s',
OPEN_METRICS_BITS_SECOND = 'bits_per_second',
AWS_KILOBITS_SECOND = 'Kilobits/Second',
UCUM_KILOBITS_SECOND = 'kbit/s',
OPEN_METRICS_KILOBITS_SECOND = 'kilobits_per_second',
AWS_MEGABITS_SECOND = 'Megabits/Second',
UCUM_MEGABITS_SECOND = 'Mbit/s',
OPEN_METRICS_MEGABITS_SECOND = 'megabits_per_second',
AWS_GIGABITS_SECOND = 'Gigabits/Second',
UCUM_GIGABITS_SECOND = 'Gbit/s',
OPEN_METRICS_GIGABITS_SECOND = 'gigabits_per_second',
AWS_TERABITS_SECOND = 'Terabits/Second',
UCUM_TERABITS_SECOND = 'Tbit/s',
OPEN_METRICS_TERABITS_SECOND = 'terabits_per_second',
AWS_COUNT = 'Count',
UCUM_COUNT = '{count}',
OPEN_METRICS_COUNT = 'count',
AWS_COUNT_SECOND = 'Count/Second',
UCUM_COUNT_SECOND = '{count}/s',
OPEN_METRICS_COUNT_SECOND = 'count_per_second',
AWS_PERCENT = 'Percent',
UCUM_PERCENT = '%',
OPEN_METRICS_PERCENT = 'ratio',
AWS_NONE = 'None',
UCUM_NONE = '1',
OPEN_METRICS_NONE = 'none',
UCUM_NANOSECONDS = 'ns',
OPEN_METRICS_NANOSECONDS = 'nanoseconds',
UCUM_MINUTES = 'min',
OPEN_METRICS_MINUTES = 'minutes',
UCUM_HOURS = 'h',
OPEN_METRICS_HOURS = 'hours',
UCUM_DAYS = 'd',
OPEN_METRICS_DAYS = 'days',
UCUM_KIBIBYTES = 'KiBy',
OPEN_METRICS_KIBIBYTES = 'kibibytes',
UCUM_MEBIBYTES = 'MiBy',
OPEN_METRICS_MEBIBYTES = 'mebibytes',
UCUM_GIBIBYTES = 'GiBy',
OPEN_METRICS_GIBIBYTES = 'gibibytes',
UCUM_TEBIBYTES = 'TiBy',
OPEN_METRICS_TEBIBYTES = 'tebibytes',
UCUM_PEBIBYTES = 'PiBy',
OPEN_METRICS_PEBIBYTES = 'pebibytes',
UCUM_KIBIBYTES_SECOND = 'KiBy/s',
OPEN_METRICS_KIBIBYTES_SECOND = 'kibibytes_per_second',
UCUM_KIBIBITS_SECOND = 'Kibit/s',
OPEN_METRICS_KIBIBITS_SECOND = 'kibibits_per_second',
UCUM_MEBIBYTES_SECOND = 'MiBy/s',
OPEN_METRICS_MEBIBYTES_SECOND = 'mebibytes_per_second',
UCUM_MEBIBITS_SECOND = 'Mibit/s',
OPEN_METRICS_MEBIBITS_SECOND = 'mebibits_per_second',
UCUM_GIBIBYTES_SECOND = 'GiBy/s',
OPEN_METRICS_GIBIBYTES_SECOND = 'gibibytes_per_second',
UCUM_GIBIBITS_SECOND = 'Gibit/s',
OPEN_METRICS_GIBIBITS_SECOND = 'gibibits_per_second',
UCUM_TEBIBYTES_SECOND = 'TiBy/s',
OPEN_METRICS_TEBIBYTES_SECOND = 'tebibytes_per_second',
UCUM_TEBIBITS_SECOND = 'Tibit/s',
OPEN_METRICS_TEBIBITS_SECOND = 'tebibits_per_second',
UCUM_PEBIBYTES_SECOND = 'PiBy/s',
OPEN_METRICS_PEBIBYTES_SECOND = 'pebibytes_per_second',
UCUM_PEBIBITS_SECOND = 'Pibit/s',
OPEN_METRICS_PEBIBITS_SECOND = 'pebibits_per_second',
UCUM_TRUE_FALSE = '{bool}',
OPEN_METRICS_TRUE_FALSE = 'boolean_true_false',
UCUM_YES_NO = '{bool}',
OPEN_METRICS_YES_NO = 'boolean_yes_no',
UCUM_COUNTS_SECOND = '{count}/s',
OPEN_METRICS_COUNTS_SECOND = 'counts_per_second',
UCUM_OPS_SECOND = '{ops}/s',
OPEN_METRICS_OPS_SECOND = 'ops_per_second',
UCUM_REQUESTS_SECOND = '{requests}/s',
OPEN_METRICS_REQUESTS_SECOND = 'requests_per_second',
UCUM_READS_SECOND = '{reads}/s',
OPEN_METRICS_READS_SECOND = 'reads_per_second',
UCUM_WRITES_SECOND = '{writes}/s',
OPEN_METRICS_WRITES_SECOND = 'writes_per_second',
UCUM_IOPS_SECOND = '{iops}/s',
OPEN_METRICS_IOPS_SECOND = 'io_ops_per_second',
UCUM_COUNTS_MINUTE = '{count}/min',
OPEN_METRICS_COUNTS_MINUTE = 'counts_per_minute',
UCUM_OPS_MINUTE = '{ops}/min',
OPEN_METRICS_OPS_MINUTE = 'ops_per_minute',
UCUM_READS_MINUTE = '{reads}/min',
OPEN_METRICS_READS_MINUTE = 'reads_per_minute',
UCUM_WRITES_MINUTE = '{writes}/min',
OPEN_METRICS_WRITES_MINUTE = 'writes_per_minute',
UCUM_PETABYTES = 'PBy',
OPEN_METRICS_PETABYTES = 'petabytes',
OPEN_METRICS_PERCENT_UNIT = 'percentunit',
}

View File

@@ -0,0 +1,33 @@
import { UniversalYAxisUnitMappings, Y_AXIS_UNIT_NAMES } from './constants';
import { UniversalYAxisUnit, YAxisUnit } from './types';
export const mapMetricUnitToUniversalUnit = (
unit: string | undefined,
): UniversalYAxisUnit | null => {
if (!unit) {
return null;
}
const universalUnit = Object.values(UniversalYAxisUnit).find(
(u) => UniversalYAxisUnitMappings[u].has(unit as YAxisUnit) || unit === u,
);
return universalUnit || (unit as UniversalYAxisUnit) || null;
};
export const getUniversalNameFromMetricUnit = (
unit: string | undefined,
): string => {
if (!unit) {
return '-';
}
const universalUnit = mapMetricUnitToUniversalUnit(unit);
if (!universalUnit) {
return unit;
}
const universalName = Y_AXIS_UNIT_NAMES[universalUnit];
return universalName || unit || '-';
};

View File

@@ -68,6 +68,13 @@
}
}
.y-axis-unit-selector-container {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
.detection-method-container {
margin: 24px 0;

View File

@@ -16,7 +16,6 @@ import {
getCategoryByOptionId,
getCategorySelectOptionByName,
} from 'container/NewWidget/RightContainer/alertFomatCategories';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useTranslation } from 'react-i18next';
import {
AlertDef,
@@ -43,10 +42,10 @@ function RuleOptions({
setAlertDef,
queryCategory,
queryOptions,
yAxisUnit,
}: RuleOptionsProps): JSX.Element {
// init namespace for translations
const { t } = useTranslation('alerts');
const { currentQuery } = useQueryBuilder();
const { ruleType } = alertDef;
@@ -365,7 +364,7 @@ function RuleOptions({
</InlineSelect>
);
const selectedCategory = getCategoryByOptionId(currentQuery?.unit || '');
const selectedCategory = getCategoryByOptionId(yAxisUnit);
const categorySelectOptions = getCategorySelectOptionByName(
selectedCategory?.name,
@@ -515,5 +514,6 @@ interface RuleOptionsProps {
setAlertDef: (a: AlertDef) => void;
queryCategory: EQueryType;
queryOptions: DefaultOptionType[];
yAxisUnit: string;
}
export default RuleOptions;

View File

@@ -5,6 +5,7 @@ import { Button, FormInstance, Modal, SelectProps, Typography } from 'antd';
import saveAlertApi from 'api/alerts/save';
import testAlertApi from 'api/alerts/testAlert';
import logEvent from 'api/common/logEvent';
import YAxisUnitSelector from 'components/YAxisUnitSelector';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { FeatureKeys } from 'constants/features';
import { QueryParams } from 'constants/query';
@@ -13,7 +14,6 @@ import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import ROUTES from 'constants/routes';
import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag';
import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag';
import { BuilderUnitsFilter } from 'container/QueryBuilder/filters';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
import { useNotifications } from 'hooks/useNotifications';
@@ -57,7 +57,7 @@ import {
StepContainer,
StepHeading,
} from './styles';
import { getSelectedQueryOptions } from './utils';
import { getSelectedQueryOptions, useGetYAxisUnitFromQuery } from './utils';
export enum AlertDetectionTypes {
THRESHOLD_ALERT = 'threshold_rule',
@@ -684,6 +684,8 @@ function FormAlertRules({
const isAlertNameMissing = !formInstance.getFieldValue('alert');
const yAxisUnitFromQuery = useGetYAxisUnitFromQuery(stagedQuery);
const onUnitChangeHandler = (value: string): void => {
setYAxisUnit(value);
// reset target unit
@@ -696,6 +698,10 @@ function FormAlertRules({
}));
};
useEffect(() => {
setYAxisUnit(yAxisUnitFromQuery || '');
}, [yAxisUnitFromQuery]);
const isChannelConfigurationValid =
alertDef?.broadcastToAll ||
(alertDef.preferredChannels && alertDef.preferredChannels.length > 0);
@@ -801,10 +807,10 @@ function FormAlertRules({
</div>
<StepContainer>
<BuilderUnitsFilter
onChange={onUnitChangeHandler}
yAxisUnit={yAxisUnit}
/>
<div className="y-axis-unit-selector-container">
<Typography.Text>Y-Axis Unit</Typography.Text>
<YAxisUnitSelector value={yAxisUnit} onChange={onUnitChangeHandler} />
</div>
</StepContainer>
<div className="steps-container">
@@ -844,6 +850,7 @@ function FormAlertRules({
alertDef={alertDef}
setAlertDef={setAlertDef}
queryOptions={queryOptions}
yAxisUnit={yAxisUnit}
/>
{renderBasicInfo()}

View File

@@ -1,13 +1,17 @@
import { SelectProps } from 'antd';
import { useGetMetricUnits } from 'container/MetricsExplorer/Explorer/utils';
import { Time } from 'container/TopNav/DateTimeSelection/config';
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
import getStep from 'lib/getStep';
import { useMemo } from 'react';
import {
IBuilderFormula,
IBuilderQuery,
IClickHouseQuery,
IPromQLQuery,
Query,
} from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
// toChartInterval converts eval window to chart selection time interval
export const toChartInterval = (evalWindow: string | undefined): Time => {
@@ -62,3 +66,31 @@ export const getSelectedQueryOptions = (
label: 'queryName' in query ? query.queryName : query.name,
value: 'queryName' in query ? query.queryName : query.name,
}));
export const useGetYAxisUnitFromQuery = (
query: Query | null,
): string | null => {
const metricNames = useMemo(() => {
if (!query) {
return [];
}
return query.builder.queryData.map((query) => query.aggregateAttribute.key);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(query)]);
const { units } = useGetMetricUnits(
metricNames,
query?.builder.queryData[0].dataSource === DataSource.METRICS,
);
return useMemo(() => {
if (!query || units.length === 0) {
return null;
}
const areAllUnitsSame = units.every((unit) => unit === units[0]);
if (areAllUnitsSame) {
return units[0];
}
return null;
}, [query, units]);
};

View File

@@ -57,6 +57,20 @@
.explore-content {
margin-top: 10px;
.y-axis-unit-selector-container {
display: flex;
align-items: center;
gap: 10px;
padding-top: 10px;
margin-bottom: 10px;
.save-unit-container {
display: flex;
align-items: center;
gap: 10px;
}
}
.ant-space {
margin-top: 10px;
margin-bottom: 20px;
@@ -74,6 +88,14 @@
.time-series-view {
min-width: 100%;
width: 100%;
position: relative;
.no-unit-warning {
position: absolute;
top: 30px;
right: 40px;
z-index: 1000;
}
}
.time-series-container {

View File

@@ -1,7 +1,7 @@
import './Explorer.styles.scss';
import * as Sentry from '@sentry/react';
import { Switch } from 'antd';
import { Switch, Tooltip } from 'antd';
import logEvent from 'api/common/logEvent';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import ExplorerOptionWrapper from 'container/ExplorerOptions/ExplorerOptionWrapper';
@@ -20,10 +20,11 @@ import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToD
import { v4 as uuid } from 'uuid';
import { MetricsExplorerEventKeys, MetricsExplorerEvents } from '../events';
import MetricDetails from '../MetricDetails/MetricDetails';
import QuerySection from './QuerySection';
import TimeSeries from './TimeSeries';
import { ExplorerTabs } from './types';
import { splitQueryIntoOneChartPerQuery } from './utils';
import { splitQueryIntoOneChartPerQuery, useGetMetricUnits } from './utils';
const ONE_CHART_PER_QUERY_ENABLED_KEY = 'isOneChartPerQueryEnabled';
@@ -35,6 +36,31 @@ function Explorer(): JSX.Element {
currentQuery,
} = useQueryBuilder();
const { safeNavigate } = useSafeNavigate();
const [isMetricDetailsOpen, setIsMetricDetailsOpen] = useState(false);
const metricNames = useMemo(
() =>
stagedQuery?.builder.queryData.map(
(query) => query.aggregateAttribute.key,
) ?? [],
[stagedQuery],
);
const {
units,
metrics,
isLoading: isMetricUnitsLoading,
isError: isMetricUnitsError,
} = useGetMetricUnits(metricNames);
const areAllMetricUnitsSame = useMemo(
() =>
!isMetricUnitsLoading &&
!isMetricUnitsError &&
units.length > 0 &&
units.every((unit) => unit === units[0]),
[units, isMetricUnitsLoading, isMetricUnitsError],
);
const [searchParams, setSearchParams] = useSearchParams();
const isOneChartPerQueryEnabled =
@@ -43,7 +69,31 @@ function Explorer(): JSX.Element {
const [showOneChartPerQuery, toggleShowOneChartPerQuery] = useState(
isOneChartPerQueryEnabled,
);
const [disableOneChartPerQuery, toggleDisableOneChartPerQuery] = useState(
false,
);
const [selectedTab] = useState<ExplorerTabs>(ExplorerTabs.TIME_SERIES);
const [yAxisUnit, setYAxisUnit] = useState<string>('');
useEffect(() => {
if (units.length === 0) {
setYAxisUnit('');
} else if (units.length === 1 && units[0] !== '') {
setYAxisUnit(units[0]);
} else if (areAllMetricUnitsSame && units[0] !== '') {
setYAxisUnit(units[0]);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(units), areAllMetricUnitsSame]);
useEffect(() => {
if (units.length > 1 && !areAllMetricUnitsSame) {
toggleShowOneChartPerQuery(true);
toggleDisableOneChartPerQuery(true);
} else {
toggleDisableOneChartPerQuery(false);
}
}, [units, areAllMetricUnitsSame]);
const handleToggleShowOneChartPerQuery = (): void => {
toggleShowOneChartPerQuery(!showOneChartPerQuery);
@@ -53,15 +103,20 @@ function Explorer(): JSX.Element {
});
};
const exportDefaultQuery = useMemo(
() =>
updateAllQueriesOperators(
currentQuery || initialQueriesMap[DataSource.METRICS],
PANEL_TYPES.TIME_SERIES,
DataSource.METRICS,
),
[currentQuery, updateAllQueriesOperators],
);
const exportDefaultQuery = useMemo(() => {
const query = updateAllQueriesOperators(
currentQuery || initialQueriesMap[DataSource.METRICS],
PANEL_TYPES.TIME_SERIES,
DataSource.METRICS,
);
if (yAxisUnit) {
return {
...query,
unit: yAxisUnit,
};
}
return query;
}, [currentQuery, updateAllQueriesOperators, yAxisUnit]);
useShareBuilderUrl(exportDefaultQuery);
@@ -75,8 +130,16 @@ function Explorer(): JSX.Element {
const widgetId = uuid();
let query = queryToExport || exportDefaultQuery;
if (yAxisUnit) {
query = {
...query,
unit: yAxisUnit,
};
}
const dashboardEditView = generateExportToDashboardLink({
query: queryToExport || exportDefaultQuery,
query,
panelType: PANEL_TYPES.TIME_SERIES,
dashboardId: dashboard.id,
widgetId,
@@ -84,7 +147,7 @@ function Explorer(): JSX.Element {
safeNavigate(dashboardEditView);
},
[exportDefaultQuery, safeNavigate],
[exportDefaultQuery, safeNavigate, yAxisUnit],
);
const splitedQueries = useMemo(
@@ -107,11 +170,17 @@ function Explorer(): JSX.Element {
<div className="explore-header">
<div className="explore-header-left-actions">
<span>1 chart/query</span>
<Switch
checked={showOneChartPerQuery}
onChange={handleToggleShowOneChartPerQuery}
size="small"
/>
<Tooltip
open={disableOneChartPerQuery ? undefined : false}
title="One chart per query cannot be disabled for multiple queries with different units."
>
<Switch
checked={showOneChartPerQuery}
onChange={handleToggleShowOneChartPerQuery}
disabled={disableOneChartPerQuery}
size="small"
/>
</Tooltip>
</div>
<div className="explore-header-right-actions">
<DateTimeSelector showAutoRefresh />
@@ -142,7 +211,18 @@ function Explorer(): JSX.Element {
</Button.Group> */}
<div className="explore-content">
{selectedTab === ExplorerTabs.TIME_SERIES && (
<TimeSeries showOneChartPerQuery={showOneChartPerQuery} />
<TimeSeries
showOneChartPerQuery={showOneChartPerQuery}
areAllMetricUnitsSame={areAllMetricUnitsSame}
isMetricUnitsLoading={isMetricUnitsLoading}
isMetricUnitsError={isMetricUnitsError}
metricUnits={units}
metricNames={metricNames}
metrics={metrics}
setIsMetricDetailsOpen={setIsMetricDetailsOpen}
yAxisUnit={yAxisUnit}
setYAxisUnit={setYAxisUnit}
/>
)}
{/* TODO: Enable once we have resolved all related metrics issues */}
{/* {selectedTab === ExplorerTabs.RELATED_METRICS && (
@@ -158,6 +238,14 @@ function Explorer(): JSX.Element {
isOneChartPerQuery={showOneChartPerQuery}
splitedQueries={splitedQueries}
/>
{isMetricDetailsOpen && (
<MetricDetails
metricName={metricNames[0]}
isOpen={isMetricDetailsOpen}
onClose={(): void => setIsMetricDetailsOpen(false)}
isModalTimeSelection={false}
/>
)}
</Sentry.ErrorBoundary>
);
}

View File

@@ -1,14 +1,20 @@
import { Color } from '@signozhq/design-tokens';
import { Button, Tooltip, Typography } from 'antd';
import { MetricType } from 'api/metricsExplorer/getMetricsList';
import classNames from 'classnames';
import YAxisUnitSelector from 'components/YAxisUnitSelector';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { BuilderUnitsFilter } from 'container/QueryBuilder/filters/BuilderUnitsFilter/BuilderUnits';
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
import { convertDataValueToMs } from 'container/TimeSeriesView/utils';
import { useUpdateMetricMetadata } from 'hooks/metricsExplorer/useUpdateMetricMetadata';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useNotifications } from 'hooks/useNotifications';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { useMemo, useState } from 'react';
import { useQueries } from 'react-query';
import { AlertTriangle } from 'lucide-react';
import { useMemo } from 'react';
import { useQueries, useQueryClient } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api';
@@ -19,8 +25,21 @@ import { GlobalReducer } from 'types/reducer/globalTime';
import { TimeSeriesProps } from './types';
import { splitQueryIntoOneChartPerQuery } from './utils';
function TimeSeries({ showOneChartPerQuery }: TimeSeriesProps): JSX.Element {
function TimeSeries({
showOneChartPerQuery,
areAllMetricUnitsSame,
isMetricUnitsLoading,
isMetricUnitsError,
metricUnits,
metricNames,
metrics,
setIsMetricDetailsOpen,
yAxisUnit,
setYAxisUnit,
}: TimeSeriesProps): JSX.Element {
const { stagedQuery, currentQuery } = useQueryBuilder();
const { notifications } = useNotifications();
const queryClient = useQueryClient();
const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector<
AppState,
@@ -56,8 +75,6 @@ function TimeSeries({ showOneChartPerQuery }: TimeSeriesProps): JSX.Element {
[showOneChartPerQuery, stagedQuery],
);
const [yAxisUnit, setYAxisUnit] = useState<string>('');
const queries = useQueries(
queryPayloads.map((payload, index) => ({
queryKey: [
@@ -105,30 +122,137 @@ function TimeSeries({ showOneChartPerQuery }: TimeSeriesProps): JSX.Element {
setYAxisUnit(value);
};
const goToMetricDetails = (): void => {
setIsMetricDetailsOpen(true);
};
const showYAxisUnitSelector = useMemo(() => {
if (metricUnits.length <= 1) {
return true;
}
if (areAllMetricUnitsSame) {
return metricUnits[0] !== '';
}
return false;
}, [metricUnits, areAllMetricUnitsSame]);
const showSaveUnitButton = useMemo(
() =>
metricUnits.length === 1 &&
Boolean(metrics?.[0]) &&
metricUnits[0] === '' &&
yAxisUnit !== '',
[metricUnits, metrics, yAxisUnit],
);
const {
mutate: updateMetricMetadata,
isLoading: isUpdatingMetricMetadata,
} = useUpdateMetricMetadata();
const handleSaveUnit = (): void => {
updateMetricMetadata(
{
metricName: metricNames[0],
payload: {
unit: yAxisUnit,
description: metrics[0]?.metadata?.description ?? '',
metricType: metrics[0]?.metadata?.metric_type as MetricType,
},
},
{
onSuccess: () => {
notifications.success({
message: 'Unit saved successfully',
});
queryClient.invalidateQueries([
REACT_QUERY_KEY.GET_METRIC_DETAILS,
metricNames[0],
]);
},
onError: () => {
notifications.error({
message: 'Failed to save unit',
});
},
},
);
};
return (
<>
<BuilderUnitsFilter onChange={onUnitChangeHandler} yAxisUnit={yAxisUnit} />
<div className="y-axis-unit-selector-container">
{showYAxisUnitSelector && (
<>
<YAxisUnitSelector
value={yAxisUnit}
onChange={onUnitChangeHandler}
loading={isMetricUnitsLoading}
disabled={isMetricUnitsLoading || isMetricUnitsError}
/>
{showSaveUnitButton && (
<div className="save-unit-container">
<Typography.Text>
Save the selected unit for this metric?
</Typography.Text>
<Button
type="primary"
size="small"
loading={isUpdatingMetricMetadata}
onClick={handleSaveUnit}
>
Yes
</Button>
</div>
)}
</>
)}
</div>
<div
className={classNames({
'time-series-container': changeLayoutForOneChartPerQuery,
})}
>
{responseData.map((datapoint, index) => (
<div
className="time-series-view"
// eslint-disable-next-line react/no-array-index-key
key={index}
>
<TimeSeriesView
isFilterApplied={false}
isError={queries[index].isError}
isLoading={queries[index].isLoading}
data={datapoint}
yAxisUnit={yAxisUnit}
dataSource={DataSource.METRICS}
/>
</div>
))}
{responseData.map((datapoint, index) => {
const isMetricUnitEmpty =
!queries[index].isLoading &&
!isMetricUnitsLoading &&
metricUnits.length > 1 &&
metricUnits[index] === '';
return (
<div
className="time-series-view"
// eslint-disable-next-line react/no-array-index-key
key={index}
>
{isMetricUnitEmpty && (
<Tooltip
className="no-unit-warning"
title={
<Typography.Text>
This metric does not have a unit. Please set one for it in the{' '}
<Typography.Link onClick={goToMetricDetails}>
metric details
</Typography.Link>{' '}
drawer.
</Typography.Text>
}
>
<AlertTriangle size={16} color={Color.BG_AMBER_400} />
</Tooltip>
)}
<TimeSeriesView
isFilterApplied={false}
isError={queries[index].isError}
isLoading={queries[index].isLoading}
data={datapoint}
yAxisUnit={yAxisUnit}
dataSource={DataSource.METRICS}
/>
</div>
);
})}
</div>
</>
);

View File

@@ -1,3 +1,4 @@
import { MetricDetails } from 'api/metricsExplorer/getMetricDetails';
import { RelatedMetric } from 'api/metricsExplorer/getRelatedMetrics';
import { UseQueryResult } from 'react-query';
import { SuccessResponse } from 'types/api';
@@ -10,6 +11,15 @@ export enum ExplorerTabs {
export interface TimeSeriesProps {
showOneChartPerQuery: boolean;
areAllMetricUnitsSame: boolean;
isMetricUnitsLoading: boolean;
isMetricUnitsError: boolean;
metricUnits: string[];
metricNames: string[];
metrics: (MetricDetails | undefined)[];
setIsMetricDetailsOpen: (isOpen: boolean) => void;
yAxisUnit: string;
setYAxisUnit: (unit: string) => void;
}
export interface RelatedMetricsProps {

View File

@@ -1,3 +1,5 @@
import { MetricDetails } from 'api/metricsExplorer/getMetricDetails';
import { useGetMultipleMetrics } from 'hooks/metricsExplorer/useGetMultipleMetrics';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { v4 as uuid } from 'uuid';
@@ -35,3 +37,25 @@ export const splitQueryIntoOneChartPerQuery = (query: Query): Query[] => {
return queries;
};
export function useGetMetricUnits(
metricNames: string[],
isEnabled = true,
): {
isLoading: boolean;
units: string[];
isError: boolean;
metrics: (MetricDetails | undefined)[];
} {
const metricsData = useGetMultipleMetrics(metricNames, {
enabled: metricNames.length > 0 && isEnabled,
});
return {
isLoading: metricsData.some((metric) => metric.isLoading),
units: metricsData.map(
(metric) => metric.data?.payload?.data?.metadata?.unit ?? '',
),
metrics: metricsData.map((metric) => metric.data?.payload?.data),
isError: metricsData.some((metric) => metric.isError),
};
}

View File

@@ -5,6 +5,8 @@ import { Temporality } from 'api/metricsExplorer/getMetricDetails';
import { MetricType } from 'api/metricsExplorer/getMetricsList';
import { UpdateMetricMetadataProps } from 'api/metricsExplorer/updateMetricMetadata';
import { ResizeTable } from 'components/ResizeTable';
import YAxisUnitSelector from 'components/YAxisUnitSelector';
import { getUniversalNameFromMetricUnit } from 'components/YAxisUnitSelector/utils';
import FieldRenderer from 'container/LogDetailedView/FieldRenderer';
import { DataType } from 'container/LogDetailedView/TableView';
import { useUpdateMetricMetadata } from 'hooks/metricsExplorer/useUpdateMetricMetadata';
@@ -35,6 +37,7 @@ function Metadata({
metricType: metadata?.metric_type || MetricType.SUM,
description: metadata?.description || '',
temporality: metadata?.temporality,
unit: metadata?.unit,
});
const { notifications } = useNotifications();
const {
@@ -66,6 +69,7 @@ function Metadata({
);
const columns: ColumnsType<DataType> = useMemo(
// eslint-disable-next-line sonarjs/cognitive-complexity
() => [
{
title: 'Key',
@@ -91,7 +95,10 @@ function Metadata({
ellipsis: true,
className: 'metric-metadata-value',
render: (field: { value: string; key: string }): JSX.Element => {
if (!isEditing || field.key === 'unit') {
// Don't allow editing of unit if it's already set
const disableEditingForMetricsWithUnits =
field.key === 'unit' && Boolean(metadata?.unit);
if (!isEditing || disableEditingForMetricsWithUnits) {
if (field.key === 'metric_type') {
return (
<div>
@@ -99,7 +106,11 @@ function Metadata({
</div>
);
}
return <FieldRenderer field={field.value || '-'} />;
let fieldValue = field.value;
if (field.key === 'unit') {
fieldValue = getUniversalNameFromMetricUnit(field.value);
}
return <FieldRenderer field={fieldValue || '-'} />;
}
if (field.key === 'metric_type') {
return (
@@ -118,6 +129,16 @@ function Metadata({
/>
);
}
if (field.key === 'unit') {
return (
<YAxisUnitSelector
value={metricMetadata.unit}
onChange={(value): void => {
setMetricMetadata((prev) => ({ ...prev, unit: value }));
}}
/>
);
}
if (field.key === 'temporality') {
return (
<Select
@@ -154,7 +175,7 @@ function Metadata({
},
},
],
[isEditing, metricMetadata, setMetricMetadata],
[isEditing, metadata?.unit, metricMetadata],
);
const handleSave = useCallback(() => {

View File

@@ -123,16 +123,18 @@ function MetricDetails({
<Typography.Text>{metric?.name}</Typography.Text>
</div>
<div className="metric-details-header-buttons">
<Button
onClick={goToMetricsExplorerwithSelectedMetric}
icon={<Compass size={16} />}
disabled={!metricName}
data-testid="open-in-explorer-button"
>
Open in Explorer
</Button>
{openInspectModal && (
<Button
onClick={goToMetricsExplorerwithSelectedMetric}
icon={<Compass size={16} />}
disabled={!metricName}
data-testid="open-in-explorer-button"
>
Open in Explorer
</Button>
)}
{/* Show the based on the feature flag. Will remove before releasing the feature */}
{showInspectFeature && (
{showInspectFeature && openInspectModal && (
<Button
className="inspect-metrics-button"
aria-label="Inspect Metric"

View File

@@ -1,6 +1,7 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { MetricDetails } from 'api/metricsExplorer/getMetricDetails';
import { MetricType } from 'api/metricsExplorer/getMetricsList';
import { getUniversalNameFromMetricUnit } from 'components/YAxisUnitSelector/utils';
import ROUTES from 'constants/routes';
import * as useGetMetricDetails from 'hooks/metricsExplorer/useGetMetricDetails';
import * as useUpdateMetricMetadata from 'hooks/metricsExplorer/useUpdateMetricMetadata';
@@ -95,7 +96,9 @@ describe('MetricDetails', () => {
expect(screen.getByText(mockMetricName)).toBeInTheDocument();
expect(screen.getByText(mockMetricDescription)).toBeInTheDocument();
expect(screen.getByText(`${mockMetricData.unit}`)).toBeInTheDocument();
expect(
screen.getByText(`${getUniversalNameFromMetricUnit(mockMetricData.unit)}`),
).toBeInTheDocument();
});
it('renders the "open in explorer" and "inspect" buttons', () => {

View File

@@ -11,7 +11,7 @@ export interface MetricDetailsProps {
isOpen: boolean;
metricName: string | null;
isModalTimeSelection: boolean;
openInspectModal: (metricName: string) => void;
openInspectModal?: (metricName: string) => void;
}
export interface DashboardsAndAlertsPopoverProps {

View File

@@ -1,6 +1,7 @@
import { Color } from '@signozhq/design-tokens';
import { render } from '@testing-library/react';
import { MetricType } from 'api/metricsExplorer/getMetricsList';
import { getUniversalNameFromMetricUnit } from 'components/YAxisUnitSelector/utils';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { TreemapViewType } from '../types';
@@ -144,7 +145,9 @@ describe('formatDataForMetricsTable', () => {
// Verify unit rendering
const unitElement = result[0].unit as JSX.Element;
const { container: unitWrapper } = render(unitElement);
expect(unitWrapper.textContent).toBe('bytes');
expect(unitWrapper.textContent).toBe(
getUniversalNameFromMetricUnit(mockData[0].unit),
);
// Verify samples rendering
const samplesElement = result[0][TreemapViewType.SAMPLES] as JSX.Element;
@@ -187,7 +190,9 @@ describe('formatDataForMetricsTable', () => {
// Verify null unit rendering
const unitElement = result[0].unit as JSX.Element;
const { container: unitWrapper } = render(unitElement);
expect(unitWrapper.textContent).toBe('ms');
expect(unitWrapper.textContent).toBe(
getUniversalNameFromMetricUnit(mockData[0].unit),
);
// Verify zero samples rendering
const samplesElement = result[0][TreemapViewType.SAMPLES] as JSX.Element;

View File

@@ -10,6 +10,7 @@ import {
SamplesData,
TimeseriesData,
} from 'api/metricsExplorer/getMetricsTreeMap';
import { getUniversalNameFromMetricUnit } from 'components/YAxisUnitSelector/utils';
import {
BarChart,
BarChart2,
@@ -200,7 +201,7 @@ export const formatDataForMetricsTable = (
metric_type: <MetricTypeRenderer type={metric.type} />,
unit: (
<ValidateRowValueWrapper value={metric.unit}>
{metric.unit}
{getUniversalNameFromMetricUnit(metric.unit)}
</ValidateRowValueWrapper>
),
[TreemapViewType.SAMPLES]: (

View File

@@ -190,7 +190,8 @@
text-transform: uppercase;
}
.y-axis-unit-selector {
.y-axis-unit-selector,
.y-axis-unit-selector-v2 {
margin-top: 16px;
display: flex;
flex-direction: column;
@@ -223,6 +224,21 @@
}
}
}
.y-axis-unit-selector-v2 {
.y-axis-unit-selector-component {
.ant-select {
width: 100%;
border-radius: 2px;
background: var(--bg-ink-300);
.ant-select-selector {
border: 1px solid var(--bg-slate-400);
}
}
}
}
.soft-min-max {
display: flex;
align-items: center;

View File

@@ -0,0 +1,23 @@
import { Typography } from 'antd';
import YAxisUnitSelectorComponent from 'components/YAxisUnitSelector';
import { Dispatch, SetStateAction } from 'react';
type OnSelectType = Dispatch<SetStateAction<string>> | ((val: string) => void);
function YAxisUnitSelectorV2({
defaultValue,
onSelect,
fieldLabel,
}: {
defaultValue: string;
onSelect: OnSelectType;
fieldLabel: string;
}): JSX.Element {
return (
<div className="y-axis-unit-selector-v2">
<Typography.Text className="heading">{fieldLabel}</Typography.Text>
<YAxisUnitSelectorComponent value={defaultValue} onChange={onSelect} />
</div>
);
}
export default YAxisUnitSelectorV2;

View File

@@ -0,0 +1,72 @@
import { Y_AXIS_CATEGORIES } from 'components/YAxisUnitSelector/constants';
import {
alertsCategory,
getCategoryByOptionId,
getCategorySelectOptionByName,
} from '../alertFomatCategories';
import { CategoryNames, DataRateFormats, MiscellaneousFormats } from '../types';
describe('getCategorySelectOptionByName', () => {
it('should return empty array for undefined category name', () => {
const result = getCategorySelectOptionByName(undefined);
expect(result).toEqual([]);
});
it('should return empty array for invalid category name', () => {
const result = getCategorySelectOptionByName('invalid_category');
expect(result).toEqual([]);
});
it('should return correct options for legacy Data Rate category', () => {
const result = getCategorySelectOptionByName(CategoryNames.DataRate);
expect(result).toEqual(
alertsCategory
.find((category) => category.name === CategoryNames.DataRate)
?.formats.map((format) => ({
label: format.name,
value: format.id,
})),
);
});
it('should return correct options for legacy Miscellaneous category', () => {
const result = getCategorySelectOptionByName(CategoryNames.Miscellaneous);
expect(result).toContainEqual({
label: 'Percent (0 - 100)',
value: MiscellaneousFormats.Percent,
});
});
});
describe('getCategoryByOptionId', () => {
it('should return undefined for invalid option id', () => {
const result = getCategoryByOptionId('invalid_id');
expect(result).toBeUndefined();
});
it('should return correct category for legacy Data Rate format id', () => {
const result = getCategoryByOptionId(DataRateFormats.BytesPerSecSI);
expect(result?.name).toBe(CategoryNames.DataRate);
});
it('should return correct category for legacy Miscellaneous format id', () => {
const result = getCategoryByOptionId(MiscellaneousFormats.Percent);
expect(result?.name).toBe(CategoryNames.Miscellaneous);
});
it('should return correct category for new Y axis unit id', () => {
const testCategory = Y_AXIS_CATEGORIES[0];
const testUnit = testCategory.units[0];
const result = getCategoryByOptionId(testUnit.id);
expect(result).toEqual({
name: testCategory.name,
formats: Y_AXIS_CATEGORIES.find(
(category) => category.name === testCategory.name,
)?.units.map((format) => ({
name: format.name,
id: format.id,
})),
});
});
});

View File

@@ -1,4 +1,5 @@
import { DefaultOptionType } from 'antd/es/select';
import { Y_AXIS_CATEGORIES } from 'components/YAxisUnitSelector/constants';
import {
BooleanFormats,
@@ -107,18 +108,53 @@ export const alertsCategory = [
export const getCategorySelectOptionByName = (
name?: CategoryNames | string,
): DefaultOptionType[] =>
alertsCategory
): DefaultOptionType[] => {
const newAlertsCategory = Y_AXIS_CATEGORIES.find(
(category) => category.name === name,
);
if (newAlertsCategory) {
return newAlertsCategory.units.map((unit) => ({
label: unit.name,
value: unit.id,
}));
}
const oldAlertsCategory = alertsCategory
.find((category) => category.name === name)
?.formats.map((format) => ({
label: format.name,
value: format.id,
})) || [];
}));
if (oldAlertsCategory) {
return oldAlertsCategory;
}
export const getCategoryByOptionId = (id: string): Category | undefined =>
alertsCategory.find((category) =>
return [];
};
export const getCategoryByOptionId = (id: string): Category | undefined => {
const newAlertsCategory = Y_AXIS_CATEGORIES.find((category) =>
category.units.some((unit) => unit.id === id),
);
if (newAlertsCategory) {
return {
name: newAlertsCategory.name,
formats: newAlertsCategory.units.map((unit) => ({
name: unit.name,
id: unit.id,
})),
};
}
const oldAlertsCategory = alertsCategory.find((category) =>
category.formats.some((format) => format.id === id),
);
if (oldAlertsCategory) {
return oldAlertsCategory;
}
return undefined;
};
export const isCategoryName = (name: string): name is CategoryNames =>
alertsCategory.some((category) => category.name === name);

View File

@@ -60,7 +60,7 @@ import LegendColors from './LegendColors/LegendColors';
import ThresholdSelector from './Threshold/ThresholdSelector';
import { ThresholdProps } from './Threshold/types';
import { timePreferance } from './timeItems';
import YAxisUnitSelector from './YAxisUnitSelector';
import YAxisUnitSelectorV2 from './YAxisUnitSelectorV2';
const { TextArea } = Input;
const { Option } = Select;
@@ -332,7 +332,7 @@ function RightContainer({
)}
{allowYAxisUnit && (
<YAxisUnitSelector
<YAxisUnitSelectorV2
defaultValue={yAxisUnit}
onSelect={setYAxisUnit}
fieldLabel={

View File

@@ -46,6 +46,7 @@ import {
import { Props } from 'types/api/dashboard/update';
import { IField } from 'types/api/logs/fields';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
@@ -238,6 +239,19 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
selectedWidget?.columnUnits || {},
);
useEffect(() => {
const compositeQuery = query.get('compositeQuery');
if (compositeQuery) {
try {
const decoded = decodeURIComponent(compositeQuery);
const parsedQuery = JSON.parse(decoded) as Query;
setYAxisUnit(parsedQuery.unit || 'none');
} catch (error) {
setYAxisUnit('none');
}
}
}, [query]);
useEffect(() => {
setSelectedWidget((prev) => {
if (!prev) {

View File

@@ -0,0 +1,35 @@
import {
getMetricDetails,
MetricDetailsResponse,
} from 'api/metricsExplorer/getMetricDetails';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useQueries, UseQueryOptions, UseQueryResult } from 'react-query';
import { ErrorResponse, SuccessResponse } from 'types/api';
type QueryData = SuccessResponse<MetricDetailsResponse> | ErrorResponse;
type QueryResult = UseQueryResult<QueryData, Error>;
type UseGetMultipleMetrics = (
metricNames: string[],
options?: UseQueryOptions<QueryData, Error>,
headers?: Record<string, string>,
) => QueryResult[];
export const useGetMultipleMetrics: UseGetMultipleMetrics = (
metricNames,
options,
headers,
) => {
const queries = useQueries(
metricNames.map(
(metricName) =>
({
queryKey: [REACT_QUERY_KEY.GET_METRIC_DETAILS, metricName],
queryFn: ({ signal }) => getMetricDetails(metricName, signal, headers),
...options,
} as UseQueryOptions<QueryData, Error>),
),
);
return queries as QueryResult[];
};

View File

@@ -1,3 +1,6 @@
import { Y_AXIS_UNIT_NAMES } from 'components/YAxisUnitSelector/constants';
import { UniversalYAxisUnit } from 'components/YAxisUnitSelector/types';
const unitsMapping = [
{
label: 'Data',
@@ -72,6 +75,68 @@ const unitsMapping = [
value: 'decpbytes',
factor: 1000 * 1000 * 1000 * 1000 * 1000,
},
// Universal units
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.BYTES],
value: UniversalYAxisUnit.BYTES,
factor: 1,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.KILOBYTES],
value: UniversalYAxisUnit.KILOBYTES,
factor: 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MEGABYTES],
value: UniversalYAxisUnit.MEGABYTES,
factor: 1024 * 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.GIGABYTES],
value: UniversalYAxisUnit.GIGABYTES,
factor: 1024 * 1024 * 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.TERABYTES],
value: UniversalYAxisUnit.TERABYTES,
factor: 1024 * 1024 * 1024 * 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PETABYTES],
value: UniversalYAxisUnit.PETABYTES,
factor: 1024 * 1024 * 1024 * 1024 * 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.BITS],
value: UniversalYAxisUnit.BITS,
factor: 8,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.KILOBITS],
value: UniversalYAxisUnit.KILOBITS,
factor: 8 * 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MEGABITS],
value: UniversalYAxisUnit.MEGABITS,
factor: 8 * 1024 * 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.GIGABITS],
value: UniversalYAxisUnit.GIGABITS,
factor: 8 * 1024 * 1024 * 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.TERABITS],
value: UniversalYAxisUnit.TERABITS,
factor: 8 * 1024 * 1024 * 1024 * 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PETABITS],
value: UniversalYAxisUnit.PETABITS,
factor: 8 * 1024 * 1024 * 1024 * 1024 * 1024,
},
],
},
{
@@ -130,6 +195,72 @@ const unitsMapping = [
// ... (other options)
],
},
// Universal units for data rate
{
label: 'Data Rate',
options: [
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.BYTES_SECOND],
value: UniversalYAxisUnit.BYTES_SECOND,
factor: 1,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.KILOBYTES_SECOND],
value: UniversalYAxisUnit.KILOBYTES_SECOND,
factor: 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MEGABYTES_SECOND],
value: UniversalYAxisUnit.MEGABYTES_SECOND,
factor: 1024 * 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.GIGABYTES_SECOND],
value: UniversalYAxisUnit.GIGABYTES_SECOND,
factor: 1024 * 1024 * 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.TERABYTES_SECOND],
value: UniversalYAxisUnit.TERABYTES_SECOND,
factor: 1024 * 1024 * 1024 * 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PETABYTES_SECOND],
value: UniversalYAxisUnit.PETABYTES_SECOND,
factor: 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.BITS_SECOND],
value: UniversalYAxisUnit.BITS_SECOND,
factor: 8,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.KILOBITS_SECOND],
value: UniversalYAxisUnit.KILOBITS_SECOND,
factor: 8 * 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MEGABITS_SECOND],
value: UniversalYAxisUnit.MEGABITS_SECOND,
factor: 8 * 1024 * 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.GIGABITS_SECOND],
value: UniversalYAxisUnit.GIGABITS_SECOND,
factor: 8 * 1024 * 1024 * 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.TERABITS_SECOND],
value: UniversalYAxisUnit.TERABITS_SECOND,
factor: 8 * 1024 * 1024 * 1024 * 1024,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PETABITS_SECOND],
value: UniversalYAxisUnit.PETABITS_SECOND,
factor: 8 * 1024 * 1024 * 1024 * 1024 * 1024,
},
],
},
{
label: 'Time',
options: [
@@ -168,6 +299,12 @@ const unitsMapping = [
value: 'd',
factor: 24 * 60 * 60 * 1000 * 1000 * 1000, // 1 d = 24 h
},
// Universal units for time
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MINUTES],
value: UniversalYAxisUnit.MINUTES,
factor: 60 * 1000 * 1000 * 1000, // 1 m = 60 s
},
],
},
{
@@ -223,7 +360,63 @@ const unitsMapping = [
value: 'wpm',
factor: 60, // 1 wpm = 60 wps
},
// ... (other options)
// Universal units for throughput
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.COUNT_SECOND],
value: UniversalYAxisUnit.COUNT_SECOND,
factor: 1,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.COUNT_MINUTE],
value: UniversalYAxisUnit.COUNT_MINUTE,
factor: 60,
},
],
},
// Universal units for operations
{
label: 'Operations',
options: [
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.OPS_SECOND],
value: UniversalYAxisUnit.OPS_SECOND,
factor: 1,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.OPS_MINUTE],
value: UniversalYAxisUnit.OPS_MINUTE,
factor: 60,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.REQUESTS_SECOND],
value: UniversalYAxisUnit.REQUESTS_SECOND,
factor: 1,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.READS_SECOND],
value: UniversalYAxisUnit.READS_SECOND,
factor: 1,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.WRITES_SECOND],
value: UniversalYAxisUnit.WRITES_SECOND,
factor: 1,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.READS_MINUTE],
value: UniversalYAxisUnit.READS_MINUTE,
factor: 60,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.WRITES_MINUTE],
value: UniversalYAxisUnit.WRITES_MINUTE,
factor: 60,
},
{
label: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.IOOPS_SECOND],
value: UniversalYAxisUnit.IOOPS_SECOND,
factor: 1,
},
],
},
{

View File

@@ -654,6 +654,11 @@ export const getUPlotChartOptions = ({
isDarkMode,
colorMapping,
}),
axes: getAxes({ isDarkMode, yAxisUnit, panelType, isLogScale }),
axes: getAxes({
isDarkMode,
yAxisUnit,
panelType,
isLogScale,
}),
};
};

View File

@@ -43,17 +43,17 @@ var (
// FromUnit returns a converter for the given unit
func FromUnit(u Unit) Converter {
switch u {
case "ns", "us", "µs", "ms", "s", "m", "h", "d":
case "ns", "us", "µs", "ms", "s", "m", "h", "d", "min":
return DurationConverter
case "bytes", "decbytes", "bits", "decbits", "kbytes", "decKbytes", "deckbytes", "mbytes", "decMbytes", "decmbytes", "gbytes", "decGbytes", "decgbytes", "tbytes", "decTbytes", "dectbytes", "pbytes", "decPbytes", "decpbytes":
case "bytes", "decbytes", "bits", "decbits", "kbytes", "decKbytes", "deckbytes", "mbytes", "decMbytes", "decmbytes", "gbytes", "decGbytes", "decgbytes", "tbytes", "decTbytes", "dectbytes", "pbytes", "decPbytes", "decpbytes", "By", "kBy", "MBy", "GBy", "TBy", "PBy":
return DataConverter
case "binBps", "Bps", "binbps", "bps", "KiBs", "Kibits", "KBs", "Kbits", "MiBs", "Mibits", "MBs", "Mbits", "GiBs", "Gibits", "GBs", "Gbits", "TiBs", "Tibits", "TBs", "Tbits", "PiBs", "Pibits", "PBs", "Pbits":
case "binBps", "Bps", "binbps", "bps", "KiBs", "Kibits", "KBs", "Kbits", "MiBs", "Mibits", "MBs", "Mbits", "GiBs", "Gibits", "GBs", "Gbits", "TiBs", "Tibits", "TBs", "Tbits", "PiBs", "Pibits", "PBs", "Pbits", "By/s", "kBy/s", "MBy/s", "GBy/s", "TBy/s", "PBy/s", "bit/s", "kbit/s", "Mbit/s", "Gbit/s", "Tbit/s", "Pbit/s":
return DataRateConverter
case "percent", "percentunit":
case "percent", "percentunit", "%":
return PercentConverter
case "bool", "bool_yes_no", "bool_true_false", "bool_1_0":
return BoolConverter
case "cps", "ops", "reqps", "rps", "wps", "iops", "cpm", "opm", "rpm", "wpm":
case "cps", "ops", "reqps", "rps", "wps", "iops", "cpm", "opm", "rpm", "wpm", "{count}/s", "{ops}/s", "{req}/s", "{read}/s", "{write}/s", "{iops}/s", "{count}/min", "{ops}/min", "{read}/min", "{write}/min":
return ThroughputConverter
default:
return NoneConverter

View File

@@ -60,7 +60,7 @@ func (*dataConverter) Name() string {
func FromDataUnit(u Unit) float64 {
switch u {
case "bytes": // base 2
case "bytes", "By": // base 2
return Byte
case "decbytes": // base 10
return Byte
@@ -68,23 +68,23 @@ func FromDataUnit(u Unit) float64 {
return Bit
case "decbits": // base 10
return Bit
case "kbytes": // base 2
case "kbytes", "kBy": // base 2
return Kibibyte
case "decKbytes", "deckbytes": // base 10
return Kilobyte
case "mbytes": // base 2
case "mbytes", "MBy": // base 2
return Mebibyte
case "decMbytes", "decmbytes": // base 10
return Megabyte
case "gbytes": // base 2
case "gbytes", "GBy": // base 2
return Gibibyte
case "decGbytes", "decgbytes": // base 10
return Gigabyte
case "tbytes": // base 2
case "tbytes", "TBy": // base 2
return Tebibyte
case "decTbytes", "dectbytes": // base 10
return Terabyte
case "pbytes": // base 2
case "pbytes", "PBy": // base 2
return Pebibyte
case "decPbytes", "decpbytes": // base 10
return Petabyte

View File

@@ -59,51 +59,51 @@ func FromDataRateUnit(u Unit) float64 {
switch u {
case "binBps": // bytes/sec(IEC)
return BytePerSecond
case "Bps": // bytes/sec(SI)
case "Bps", "By/s": // bytes/sec(SI)
return BytePerSecond
case "binbps": // bits/sec(IEC)
return BitPerSecond
case "bps": // bits/sec(SI)
case "bps", "bit/s": // bits/sec(SI)
return BitPerSecond
case "KiBs": // kibibytes/sec
return KibibytePerSecond
case "Kibits": // kibibits/sec
return KibibitPerSecond
case "KBs": // kilobytes/sec
case "KBs", "kBy/s": // kilobytes/sec
return KilobytePerSecond
case "Kbits": // kilobits/sec
case "Kbits", "kbit/s": // kilobits/sec
return KilobitPerSecond
case "MiBs": // mebibytes/sec
return MebibytePerSecond
case "Mibits": // mebibits/sec
return MebibitPerSecond
case "MBs": // megabytes/sec
case "MBs", "MBy/s": // megabytes/sec
return MegabytePerSecond
case "Mbits": // megabits/sec
case "Mbits", "Mbit/s": // megabits/sec
return MegabitPerSecond
case "GiBs": // gibibytes/sec
return GibibytePerSecond
case "Gibits": // gibibits/sec
return GibibitPerSecond
case "GBs": // gigabytes/sec
case "GBs", "GBy/s": // gigabytes/sec
return GigabytePerSecond
case "Gbits": // gigabits/sec
case "Gbits", "Gbit/s": // gigabits/sec
return GigabitPerSecond
case "TiBs": // tebibytes/sec
return TebibytePerSecond
case "Tibits": // tebibits/sec
return TebibitPerSecond
case "TBs": // terabytes/sec
case "TBs", "TBy/s": // terabytes/sec
return TerabytePerSecond
case "Tbits": // terabits/sec
case "Tbits", "Tbit/s": // terabits/sec
return TerabitPerSecond
case "PiBs": // pebibytes/sec
return PebibytePerSecond
case "Pibits": // pebibits/sec
return PebibitPerSecond
case "PBs": // petabytes/sec
case "PBs", "PBy/s": // petabytes/sec
return PetabytePerSecond
case "Pbits": // petabits/sec
case "Pbits", "Pbit/s": // petabits/sec
return PetabitPerSecond
default:
return 1

View File

@@ -36,10 +36,16 @@ func TestDataRate(t *testing.T) {
// 8 bits = 1 byte
assert.Equal(t, Value{F: 1, U: "binBps"}, dataRateConverter.Convert(Value{F: 8, U: "binbps"}, "binBps"))
// 8 bits = 1 byte
assert.Equal(t, Value{F: 1, U: "Bps"}, dataRateConverter.Convert(Value{F: 8, U: "bps"}, "Bps"))
// 8 bits = 1 byte
assert.Equal(t, Value{F: 1, U: "By/s"}, dataRateConverter.Convert(Value{F: 8, U: "bit/s"}, "By/s"))
// 1024 bytes = 1 kbytes
assert.Equal(t, Value{F: 1, U: "KiBs"}, dataRateConverter.Convert(Value{F: 1024, U: "binBps"}, "KiBs"))
// 1 byte = 8 bits
assert.Equal(t, Value{F: 8, U: "binbps"}, dataRateConverter.Convert(Value{F: 1, U: "binBps"}, "binbps"))
// 1 byte = 8 bits
assert.Equal(t, Value{F: 8, U: "bit/s"}, dataRateConverter.Convert(Value{F: 1, U: "Bps"}, "bit/s"))
// 1 mbytes = 1024 kbytes
assert.Equal(t, Value{F: 1, U: "MiBs"}, dataRateConverter.Convert(Value{F: 1024, U: "KiBs"}, "MiBs"))
// 1 kbytes = 1024 bytes
@@ -57,6 +63,10 @@ func TestDataRate(t *testing.T) {
// 1 gbytes = 1024 * 1024 kbytes
assert.Equal(t, Value{F: 1024 * 1024, U: "KiBs"}, dataRateConverter.Convert(Value{F: 1, U: "GiBs"}, "KiBs"))
// 1 gbytes = 1024 * 1024 * 1024 bytes
assert.Equal(t, Value{F: (1024 * 1024 * 1024 * 8) / 1024, U: "Kibits"}, dataRateConverter.Convert(Value{F: 1, U: "GiBs"}, "Kibits"))
// 1 gbytes = 1024 * 1024 * 1024 bytes
assert.Equal(t, Value{F: float64(1024*1024*1024) / 1000.0, U: "kBy/s"}, dataRateConverter.Convert(Value{F: 1, U: "GiBs"}, "kBy/s"))
// 1 gbytes = 1024 * 1024 * 1024 bytes
assert.Equal(t, Value{F: 1024 * 1024 * 1024, U: "binBps"}, dataRateConverter.Convert(Value{F: 1, U: "GiBs"}, "binBps"))
// 1024 * 1024 bytes = 1 mbytes
assert.Equal(t, Value{F: 1, U: "MiBs"}, dataRateConverter.Convert(Value{F: 1024 * 1024, U: "binBps"}, "MiBs"))

View File

@@ -10,8 +10,10 @@ func TestData(t *testing.T) {
dataConverter := NewDataConverter()
// 8 bits = 1 byte
assert.Equal(t, Value{F: 1, U: "bytes"}, dataConverter.Convert(Value{F: 8, U: "bits"}, "bytes"))
assert.Equal(t, Value{F: 1, U: "By"}, dataConverter.Convert(Value{F: 8, U: "bits"}, "By"))
// 1024 bytes = 1 kbytes
assert.Equal(t, Value{F: 1, U: "kbytes"}, dataConverter.Convert(Value{F: 1024, U: "bytes"}, "kbytes"))
assert.Equal(t, Value{F: 1, U: "kBy"}, dataConverter.Convert(Value{F: 1024, U: "bytes"}, "kBy"))
// 1 byte = 8 bits
assert.Equal(t, Value{F: 8, U: "bits"}, dataConverter.Convert(Value{F: 1, U: "bytes"}, "bits"))
// 1 mbytes = 1024 kbytes
@@ -20,6 +22,7 @@ func TestData(t *testing.T) {
assert.Equal(t, Value{F: 1024, U: "bytes"}, dataConverter.Convert(Value{F: 1, U: "kbytes"}, "bytes"))
// 1024 kbytes = 1 mbytes
assert.Equal(t, Value{F: 1, U: "mbytes"}, dataConverter.Convert(Value{F: 1024, U: "kbytes"}, "mbytes"))
assert.Equal(t, Value{F: 1, U: "MBy"}, dataConverter.Convert(Value{F: 1024, U: "kbytes"}, "MBy"))
// 1 mbytes = 1024 * 1024 bytes
assert.Equal(t, Value{F: 1024 * 1024, U: "bytes"}, dataConverter.Convert(Value{F: 1, U: "mbytes"}, "bytes"))
// 1024 mbytes = 1 gbytes
@@ -42,6 +45,10 @@ func TestData(t *testing.T) {
assert.Equal(t, Value{F: 1024 * 1024 * 1024 * 1024, U: "bytes"}, dataConverter.Convert(Value{F: 1, U: "tbytes"}, "bytes"))
// 1024 tbytes = 1 pbytes
assert.Equal(t, Value{F: 1, U: "pbytes"}, dataConverter.Convert(Value{F: 1024, U: "tbytes"}, "pbytes"))
// 1024 tbytes = 1 pbytes
assert.Equal(t, Value{F: 1, U: "PBy"}, dataConverter.Convert(Value{F: 1024, U: "tbytes"}, "PBy"))
// 1 pbytes = 1024 tbytes
assert.Equal(t, Value{F: 1024, U: "tbytes"}, dataConverter.Convert(Value{F: 1, U: "pbytes"}, "tbytes"))
// 1024 pbytes = 1 tbytes
assert.Equal(t, Value{F: 1024, U: "TBy"}, dataConverter.Convert(Value{F: 1, U: "pbytes"}, "TBy"))
}

View File

@@ -13,7 +13,7 @@ func (*percentConverter) Name() string {
func FromPercentUnit(u Unit) float64 {
switch u {
case "percent":
case "percent", "%":
return 1
case "percentunit":
return 100

View File

@@ -13,4 +13,5 @@ func TestPercentConverter(t *testing.T) {
assert.Equal(t, Value{F: 100, U: "percent"}, percentConverter.Convert(Value{F: 1, U: "percentunit"}, "percent"))
assert.Equal(t, Value{F: 1, U: "percentunit"}, percentConverter.Convert(Value{F: 100, U: "percent"}, "percentunit"))
assert.Equal(t, Value{F: 0.01, U: "percentunit"}, percentConverter.Convert(Value{F: 1, U: "percent"}, "percentunit"))
assert.Equal(t, Value{F: 1, U: "percent"}, percentConverter.Convert(Value{F: 1, U: "%"}, "percent"))
}

View File

@@ -41,7 +41,7 @@ func FromTimeUnit(u Unit) Duration {
return Decisecond
case "s":
return Second
case "m":
case "m", "min":
return Minute
case "h":
return Hour

View File

@@ -24,6 +24,8 @@ func TestDurationConvert(t *testing.T) {
assert.Equal(t, Value{F: 60, U: "s"}, timeConverter.Convert(Value{F: 1, U: "m"}, "s"))
// 60 m = 1 h
assert.Equal(t, Value{F: 1, U: "h"}, timeConverter.Convert(Value{F: 60, U: "m"}, "h"))
// 60 min = 1 h
assert.Equal(t, Value{F: 1, U: "h"}, timeConverter.Convert(Value{F: 60, U: "min"}, "h"))
// 168 h = 1 w
assert.Equal(t, Value{F: 1, U: "w"}, timeConverter.Convert(Value{F: 168, U: "h"}, "w"))
// 1 h = 60 m

View File

@@ -20,7 +20,7 @@ func (*dataFormatter) Name() string {
func (f *dataFormatter) Format(value float64, unit string) string {
switch unit {
case "bytes":
case "bytes", "By":
return humanize.IBytes(uint64(value))
case "decbytes":
return humanize.Bytes(uint64(value))
@@ -28,23 +28,23 @@ func (f *dataFormatter) Format(value float64, unit string) string {
return humanize.IBytes(uint64(value * converter.Bit))
case "decbits":
return humanize.Bytes(uint64(value * converter.Bit))
case "kbytes":
case "kbytes", "kBy":
return humanize.IBytes(uint64(value * converter.Kibibit))
case "decKbytes", "deckbytes":
return humanize.IBytes(uint64(value * converter.Kilobit))
case "mbytes":
case "mbytes", "MBy":
return humanize.IBytes(uint64(value * converter.Mebibit))
case "decMbytes", "decmbytes":
return humanize.Bytes(uint64(value * converter.Megabit))
case "gbytes":
case "gbytes", "GBy":
return humanize.IBytes(uint64(value * converter.Gibibit))
case "decGbytes", "decgbytes":
return humanize.Bytes(uint64(value * converter.Gigabit))
case "tbytes":
case "tbytes", "TBy":
return humanize.IBytes(uint64(value * converter.Tebibit))
case "decTbytes", "dectbytes":
return humanize.Bytes(uint64(value * converter.Terabit))
case "pbytes":
case "pbytes", "PBy":
return humanize.IBytes(uint64(value * converter.Pebibit))
case "decPbytes", "decpbytes":
return humanize.Bytes(uint64(value * converter.Petabit))

View File

@@ -22,51 +22,51 @@ func (f *dataRateFormatter) Format(value float64, unit string) string {
switch unit {
case "binBps":
return humanize.IBytes(uint64(value)) + "/s"
case "Bps":
case "Bps", "By/s":
return humanize.Bytes(uint64(value)) + "/s"
case "binbps":
return humanize.IBytes(uint64(value*converter.BitPerSecond)) + "/s"
case "bps":
case "bps", "bit/s":
return humanize.Bytes(uint64(value*converter.BitPerSecond)) + "/s"
case "KiBs":
return humanize.IBytes(uint64(value*converter.KibibitPerSecond)) + "/s"
case "Kibits":
return humanize.IBytes(uint64(value*converter.KibibytePerSecond)) + "/s"
case "KBs":
case "KBs", "kBy/s":
return humanize.IBytes(uint64(value*converter.KilobitPerSecond)) + "/s"
case "Kbits":
case "Kbits", "kbit/s":
return humanize.IBytes(uint64(value*converter.KilobytePerSecond)) + "/s"
case "MiBs":
return humanize.IBytes(uint64(value*converter.MebibitPerSecond)) + "/s"
case "Mibits":
return humanize.IBytes(uint64(value*converter.MebibytePerSecond)) + "/s"
case "MBs":
case "MBs", "MBy/s":
return humanize.IBytes(uint64(value*converter.MegabitPerSecond)) + "/s"
case "Mbits":
case "Mbits", "Mbit/s":
return humanize.IBytes(uint64(value*converter.MegabytePerSecond)) + "/s"
case "GiBs":
return humanize.IBytes(uint64(value*converter.GibibitPerSecond)) + "/s"
case "Gibits":
return humanize.IBytes(uint64(value*converter.GibibytePerSecond)) + "/s"
case "GBs":
case "GBs", "GBy/s":
return humanize.IBytes(uint64(value*converter.GigabitPerSecond)) + "/s"
case "Gbits":
case "Gbits", "Gbit/s":
return humanize.IBytes(uint64(value*converter.GigabytePerSecond)) + "/s"
case "TiBs":
return humanize.IBytes(uint64(value*converter.TebibitPerSecond)) + "/s"
case "Tibits":
return humanize.IBytes(uint64(value*converter.TebibytePerSecond)) + "/s"
case "TBs":
case "TBs", "TBy/s":
return humanize.IBytes(uint64(value*converter.TerabitPerSecond)) + "/s"
case "Tbits":
case "Tbits", "Tbit/s":
return humanize.IBytes(uint64(value*converter.TerabytePerSecond)) + "/s"
case "PiBs":
return humanize.IBytes(uint64(value*converter.PebibitPerSecond)) + "/s"
case "Pibits":
return humanize.IBytes(uint64(value*converter.PebibytePerSecond)) + "/s"
case "PBs":
case "PBs", "PBy/s":
return humanize.IBytes(uint64(value*converter.PetabitPerSecond)) + "/s"
case "Pbits":
case "Pbits", "Pbit/s":
return humanize.IBytes(uint64(value*converter.PetabytePerSecond)) + "/s"
}
// When unit is not matched, return the value as it is.

View File

@@ -10,14 +10,25 @@ func TestData(t *testing.T) {
dataFormatter := NewDataFormatter()
assert.Equal(t, "1 B", dataFormatter.Format(1, "bytes"))
assert.Equal(t, "1 B", dataFormatter.Format(1, "By"))
assert.Equal(t, "1.0 KiB", dataFormatter.Format(1024, "bytes"))
assert.Equal(t, "1.0 KiB", dataFormatter.Format(1024, "By"))
assert.Equal(t, "2.3 GiB", dataFormatter.Format(2.3*1024, "mbytes"))
assert.Equal(t, "2.3 GiB", dataFormatter.Format(2.3*1024, "MBy"))
assert.Equal(t, "1.0 MiB", dataFormatter.Format(1024*1024, "bytes"))
assert.Equal(t, "1.0 MiB", dataFormatter.Format(1024*1024, "By"))
assert.Equal(t, "69 TiB", dataFormatter.Format(69*1024*1024, "mbytes"))
assert.Equal(t, "69 TiB", dataFormatter.Format(69*1024*1024, "MBy"))
assert.Equal(t, "102 KiB", dataFormatter.Format(102*1024, "bytes"))
assert.Equal(t, "102 KiB", dataFormatter.Format(102*1024, "By"))
assert.Equal(t, "240 MiB", dataFormatter.Format(240*1024, "kbytes"))
assert.Equal(t, "240 MiB", dataFormatter.Format(240*1024, "kBy"))
assert.Equal(t, "1.0 GiB", dataFormatter.Format(1024*1024, "kbytes"))
assert.Equal(t, "1.0 GiB", dataFormatter.Format(1024*1024, "kBy"))
assert.Equal(t, "23 GiB", dataFormatter.Format(23*1024*1024, "kbytes"))
assert.Equal(t, "23 GiB", dataFormatter.Format(23*1024*1024, "kBy"))
assert.Equal(t, "32 TiB", dataFormatter.Format(32*1024*1024*1024, "kbytes"))
assert.Equal(t, "32 TiB", dataFormatter.Format(32*1024*1024*1024, "kBy"))
assert.Equal(t, "24 MiB", dataFormatter.Format(24, "mbytes"))
assert.Equal(t, "24 MiB", dataFormatter.Format(24, "MBy"))
}

View File

@@ -18,17 +18,17 @@ var (
func FromUnit(u string) Formatter {
switch u {
case "ns", "us", "µs", "ms", "s", "m", "h", "d":
case "ns", "us", "µs", "ms", "s", "m", "h", "d", "min":
return DurationFormatter
case "bytes", "decbytes", "bits", "decbits", "kbytes", "decKbytes", "deckbytes", "mbytes", "decMbytes", "decmbytes", "gbytes", "decGbytes", "decgbytes", "tbytes", "decTbytes", "dectbytes", "pbytes", "decPbytes", "decpbytes":
case "bytes", "decbytes", "bits", "decbits", "kbytes", "decKbytes", "deckbytes", "mbytes", "decMbytes", "decmbytes", "gbytes", "decGbytes", "decgbytes", "tbytes", "decTbytes", "dectbytes", "pbytes", "decPbytes", "decpbytes", "By", "kBy", "MBy", "GBy", "TBy", "PBy":
return DataFormatter
case "binBps", "Bps", "binbps", "bps", "KiBs", "Kibits", "KBs", "Kbits", "MiBs", "Mibits", "MBs", "Mbits", "GiBs", "Gibits", "GBs", "Gbits", "TiBs", "Tibits", "TBs", "Tbits", "PiBs", "Pibits", "PBs", "Pbits":
case "binBps", "Bps", "binbps", "bps", "KiBs", "Kibits", "KBs", "Kbits", "MiBs", "Mibits", "MBs", "Mbits", "GiBs", "Gibits", "GBs", "Gbits", "TiBs", "Tibits", "TBs", "Tbits", "PiBs", "Pibits", "PBs", "Pbits", "By/s", "kBy/s", "MBy/s", "GBy/s", "TBy/s", "PBy/s", "bit/s", "kbit/s", "Mbit/s", "Gbit/s", "Tbit/s", "Pbit/s":
return DataRateFormatter
case "percent", "percentunit":
case "percent", "percentunit", "%":
return PercentFormatter
case "bool", "bool_yes_no", "bool_true_false", "bool_1_0":
return BoolFormatter
case "cps", "ops", "reqps", "rps", "wps", "iops", "cpm", "opm", "rpm", "wpm":
case "cps", "ops", "reqps", "rps", "wps", "iops", "cpm", "opm", "rpm", "wpm", "{count}/s", "{ops}/s", "{req}/s", "{read}/s", "{write}/s", "{iops}/s", "{count}/min", "{ops}/min", "{read}/min", "{write}/min":
return ThroughputFormatter
default:
return NoneFormatter

View File

@@ -22,7 +22,7 @@ func toPercentUnit(value float64, decimals DecimalCount) string {
func (f *percentFormatter) Format(value float64, unit string) string {
switch unit {
case "percent":
case "percent", "%":
return toPercent(value, nil)
case "percentunit":
return toPercentUnit(value, nil)

View File

@@ -22,25 +22,25 @@ func simpleCountUnit(value float64, decimals *int, symbol string) string {
func (f *throughputFormatter) Format(value float64, unit string) string {
switch unit {
case "cps":
case "cps", "{count}/s":
return simpleCountUnit(value, nil, "c/s")
case "ops":
case "ops", "{ops}/s":
return simpleCountUnit(value, nil, "op/s")
case "reqps":
case "reqps", "{req}/s":
return simpleCountUnit(value, nil, "req/s")
case "rps":
case "rps", "{read}/s":
return simpleCountUnit(value, nil, "r/s")
case "wps":
case "wps", "{write}/s":
return simpleCountUnit(value, nil, "w/s")
case "iops":
case "iops", "{iops}/s":
return simpleCountUnit(value, nil, "iops")
case "cpm":
case "cpm", "{count}/min":
return simpleCountUnit(value, nil, "c/m")
case "opm":
case "opm", "{ops}/min":
return simpleCountUnit(value, nil, "op/m")
case "rpm":
case "rpm", "{read}/min":
return simpleCountUnit(value, nil, "r/m")
case "wpm":
case "wpm", "{write}/min":
return simpleCountUnit(value, nil, "w/m")
}
// When unit is not matched, return the value as it is.

View File

@@ -10,6 +10,11 @@ func TestThroughput(t *testing.T) {
throughputFormatter := NewThroughputFormatter()
assert.Equal(t, "10 req/s", throughputFormatter.Format(10, "reqps"))
assert.Equal(t, "10 req/s", throughputFormatter.Format(10, "{req}/s"))
assert.Equal(t, "1K req/s", throughputFormatter.Format(1000, "reqps"))
assert.Equal(t, "1K req/s", throughputFormatter.Format(1000, "{req}/s"))
assert.Equal(t, "1M req/s", throughputFormatter.Format(1000000, "reqps"))
assert.Equal(t, "1M req/s", throughputFormatter.Format(1000000, "{req}/s"))
assert.Equal(t, "10 c/m", throughputFormatter.Format(10, "cpm"))
assert.Equal(t, "10 c/m", throughputFormatter.Format(10, "{count}/min"))
}

View File

@@ -26,7 +26,7 @@ func (f *durationFormatter) Format(value float64, unit string) string {
return toMilliSeconds(value)
case "s":
return toSeconds(value)
case "m":
case "m", "min":
return toMinutes(value)
case "h":
return toHours(value)

View File

@@ -26,4 +26,5 @@ func TestDuration(t *testing.T) {
assert.Equal(t, "1.82 min", durationFormatter.Format(109200000000, "ns"))
assert.Equal(t, "1.27 day", durationFormatter.Format(109800000000000, "ns"))
assert.Equal(t, "2 day", durationFormatter.Format(172800000, "ms"))
assert.Equal(t, "1 hour", durationFormatter.Format(60, "min"))
}

View File

@@ -1219,6 +1219,35 @@ func TestThresholdRuleUnitCombinations(t *testing.T) {
matchType: "1", // Once
target: 200, // 200 GB
},
{
targetUnit: "decgbytes",
yAxisUnit: "By",
values: [][]interface{}{
{float64(2863284053), "attr", time.Now()}, // 2.86 GB
{float64(2863388842), "attr", time.Now().Add(1 * time.Second)}, // 2.86 GB
{float64(300947400), "attr", time.Now().Add(2 * time.Second)}, // 0.3 GB
{float64(299316000), "attr", time.Now().Add(3 * time.Second)}, // 0.3 GB
{float64(66640400.00000001), "attr", time.Now().Add(4 * time.Second)}, // 66.64 MB
},
expectAlerts: 0,
compareOp: "1", // Above
matchType: "1", // Once
target: 200, // 200 GB
},
{
targetUnit: "h",
yAxisUnit: "min",
values: [][]interface{}{
{float64(55), "attr", time.Now()}, // 55 minutes
{float64(57), "attr", time.Now().Add(1 * time.Minute)}, // 57 minutes
{float64(30), "attr", time.Now().Add(2 * time.Minute)}, // 30 minutes
{float64(29), "attr", time.Now().Add(3 * time.Minute)}, // 29 minutes
},
expectAlerts: 0,
compareOp: "1", // Above
matchType: "1", // Once
target: 1, // 1 hour
},
}
for idx, c := range cases {

View File

@@ -86,5 +86,26 @@ var (
matchType: "1", // Once
target: 200, // 200 GB
},
{
targetUnit: "decgbytes",
yAxisUnit: "By",
values: [][]interface{}{
{float64(2863284053), "attr", time.Now()}, // 2.86 GB
{float64(2863388842), "attr", time.Now().Add(1 * time.Second)}, // 2.86 GB
{float64(300947400), "attr", time.Now().Add(2 * time.Second)}, // 0.3 GB
{float64(299316000), "attr", time.Now().Add(3 * time.Second)}, // 0.3 GB
{float64(66640400.00000001), "attr", time.Now().Add(4 * time.Second)}, // 66.64 MB
},
metaValues: [][]interface{}{},
createTableValues: [][]interface{}{
{"statement"},
},
attrMetaValues: [][]interface{}{},
resourceMetaValues: [][]interface{}{},
expectAlerts: 0,
compareOp: "1", // Above
matchType: "1", // Once
target: 200, // 200 GB
},
}
)