fix: y axis unit not interactive in the panel edit mode (#9003)

This commit is contained in:
SagarRajput-7 2025-09-04 16:00:03 +05:30 committed by GitHub
parent 2dbe0777f4
commit bcd21cee74
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 276 additions and 9 deletions

View File

@ -73,7 +73,6 @@ export function ColumnUnitSelector(
<Typography.Text className="heading">Column Units</Typography.Text> <Typography.Text className="heading">Column Units</Typography.Text>
{aggregationQueries.map(({ value, label }) => ( {aggregationQueries.map(({ value, label }) => (
<YAxisUnitSelector <YAxisUnitSelector
defaultValue={columnUnits[value]}
value={columnUnits[value] || ''} value={columnUnits[value] || ''}
onSelect={(unitValue: string): void => onSelect={(unitValue: string): void =>
handleColumnUnitSelect(value, unitValue) handleColumnUnitSelect(value, unitValue)

View File

@ -1,6 +1,6 @@
import { AutoComplete, Input, Typography } from 'antd'; import { AutoComplete, Input, Typography } from 'antd';
import { find } from 'lodash-es'; import { find } from 'lodash-es';
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { flattenedCategories } from './dataFormatCategories'; import { flattenedCategories } from './dataFormatCategories';
@ -14,25 +14,54 @@ const findCategoryByName = (
find(flattenedCategories, (option) => option.name === searchValue); find(flattenedCategories, (option) => option.name === searchValue);
type OnSelectType = Dispatch<SetStateAction<string>> | ((val: string) => void); type OnSelectType = Dispatch<SetStateAction<string>> | ((val: string) => void);
function YAxisUnitSelector({ function YAxisUnitSelector({
defaultValue,
value, value,
onSelect, onSelect,
fieldLabel, fieldLabel,
handleClear, handleClear,
}: { }: {
defaultValue: string;
value: string; value: string;
onSelect: OnSelectType; onSelect: OnSelectType;
fieldLabel: string; fieldLabel: string;
handleClear?: () => void; handleClear?: () => void;
}): JSX.Element { }): JSX.Element {
const [inputValue, setInputValue] = useState('');
// Sync input value with the actual value prop
useEffect(() => {
const category = findCategoryById(value);
setInputValue(category?.name || '');
}, [value]);
const onSelectHandler = (selectedValue: string): void => { const onSelectHandler = (selectedValue: string): void => {
onSelect(findCategoryByName(selectedValue)?.id || ''); const category = findCategoryByName(selectedValue);
if (category) {
onSelect(category.id);
setInputValue(selectedValue);
}
}; };
const onChangeHandler = (inputValue: string): void => {
setInputValue(inputValue);
// Clear the yAxisUnit if input is empty or doesn't match any option
if (!inputValue) {
onSelect('');
}
};
const onClearHandler = (): void => {
setInputValue('');
onSelect('');
if (handleClear) {
handleClear();
}
};
const options = flattenedCategories.map((options) => ({ const options = flattenedCategories.map((options) => ({
value: options.name, value: options.name,
})); }));
return ( return (
<div className="y-axis-unit-selector"> <div className="y-axis-unit-selector">
<Typography.Text className="heading">{fieldLabel}</Typography.Text> <Typography.Text className="heading">{fieldLabel}</Typography.Text>
@ -41,9 +70,9 @@ function YAxisUnitSelector({
rootClassName="y-axis-root-popover" rootClassName="y-axis-root-popover"
options={options} options={options}
allowClear allowClear
defaultValue={findCategoryById(defaultValue)?.name} value={inputValue}
value={findCategoryById(value)?.name || ''} onChange={onChangeHandler}
onClear={handleClear} onClear={onClearHandler}
onSelect={onSelectHandler} onSelect={onSelectHandler}
filterOption={(inputValue, option): boolean => { filterOption={(inputValue, option): boolean => {
if (option) { if (option) {

View File

@ -0,0 +1,240 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { act } from 'react-dom/test-utils';
import YAxisUnitSelector from '../YAxisUnitSelector';
// Mock the dataFormatCategories to have predictable test data
jest.mock('../dataFormatCategories', () => ({
flattenedCategories: [
{ id: 'seconds', name: 'seconds (s)' },
{ id: 'milliseconds', name: 'milliseconds (ms)' },
{ id: 'hours', name: 'hours (h)' },
{ id: 'minutes', name: 'minutes (m)' },
],
}));
const MOCK_SECONDS = 'seconds';
const MOCK_MILLISECONDS = 'milliseconds';
describe('YAxisUnitSelector', () => {
const defaultProps = {
value: MOCK_SECONDS,
onSelect: jest.fn(),
fieldLabel: 'Y Axis Unit',
handleClear: jest.fn(),
};
let user: ReturnType<typeof userEvent.setup>;
beforeEach(() => {
jest.clearAllMocks();
user = userEvent.setup();
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('Rendering (Read) & (write)', () => {
it('renders with correct field label', () => {
render(
<YAxisUnitSelector
value={defaultProps.value}
onSelect={defaultProps.onSelect}
fieldLabel={defaultProps.fieldLabel}
handleClear={defaultProps.handleClear}
/>,
);
expect(screen.getByText('Y Axis Unit')).toBeInTheDocument();
const input = screen.getByRole('combobox');
expect(input).toHaveValue('seconds (s)');
});
it('renders with custom field label', () => {
render(
<YAxisUnitSelector
value={defaultProps.value}
onSelect={defaultProps.onSelect}
fieldLabel="Custom Unit Label"
handleClear={defaultProps.handleClear}
/>,
);
expect(screen.getByText('Custom Unit Label')).toBeInTheDocument();
});
it('displays empty input when value prop is empty', () => {
render(
<YAxisUnitSelector
value=""
onSelect={defaultProps.onSelect}
fieldLabel={defaultProps.fieldLabel}
handleClear={defaultProps.handleClear}
/>,
);
expect(screen.getByDisplayValue('')).toBeInTheDocument();
});
it('shows placeholder text', () => {
render(
<YAxisUnitSelector
value={defaultProps.value}
onSelect={defaultProps.onSelect}
fieldLabel={defaultProps.fieldLabel}
handleClear={defaultProps.handleClear}
/>,
);
expect(screen.getByPlaceholderText('Unit')).toBeInTheDocument();
});
it('handles numeric input', async () => {
render(
<YAxisUnitSelector
value={defaultProps.value}
onSelect={defaultProps.onSelect}
fieldLabel={defaultProps.fieldLabel}
handleClear={defaultProps.handleClear}
/>,
);
const input = screen.getByRole('combobox');
await user.clear(input);
await user.type(input, '12345');
expect(input).toHaveValue('12345');
});
it('handles mixed content input', async () => {
render(
<YAxisUnitSelector
value={defaultProps.value}
onSelect={defaultProps.onSelect}
fieldLabel={defaultProps.fieldLabel}
handleClear={defaultProps.handleClear}
/>,
);
const input = screen.getByRole('combobox');
await user.clear(input);
await user.type(input, 'Test123!@#');
expect(input).toHaveValue('Test123!@#');
});
});
describe('State Management', () => {
it('syncs input value with value prop changes', async () => {
const { rerender } = render(
<YAxisUnitSelector
value={defaultProps.value}
onSelect={defaultProps.onSelect}
fieldLabel={defaultProps.fieldLabel}
handleClear={defaultProps.handleClear}
/>,
);
const input = screen.getByRole('combobox');
// Initial value
expect(input).toHaveValue('seconds (s)');
// Change value prop
rerender(
<YAxisUnitSelector
value={MOCK_MILLISECONDS}
onSelect={defaultProps.onSelect}
fieldLabel={defaultProps.fieldLabel}
handleClear={defaultProps.handleClear}
/>,
);
await waitFor(() => {
expect(input).toHaveValue('milliseconds (ms)');
});
});
it('handles empty value prop correctly', async () => {
const { rerender } = render(
<YAxisUnitSelector
value={defaultProps.value}
onSelect={defaultProps.onSelect}
fieldLabel={defaultProps.fieldLabel}
handleClear={defaultProps.handleClear}
/>,
);
const input = screen.getByRole('combobox');
// Change to empty value
rerender(
<YAxisUnitSelector
value=""
onSelect={defaultProps.onSelect}
fieldLabel={defaultProps.fieldLabel}
handleClear={defaultProps.handleClear}
/>,
);
await waitFor(() => {
expect(input).toHaveValue('');
});
});
it('handles invalid value prop gracefully', async () => {
const { rerender } = render(
<YAxisUnitSelector
value={defaultProps.value}
onSelect={defaultProps.onSelect}
fieldLabel={defaultProps.fieldLabel}
handleClear={defaultProps.handleClear}
/>,
);
const input = screen.getByRole('combobox');
// Change to invalid value
rerender(
<YAxisUnitSelector
value="invalid_id"
onSelect={defaultProps.onSelect}
fieldLabel={defaultProps.fieldLabel}
handleClear={defaultProps.handleClear}
/>,
);
await waitFor(() => {
expect(input).toHaveValue('');
});
});
it('maintains local state during typing', async () => {
render(
<YAxisUnitSelector
value={defaultProps.value}
onSelect={defaultProps.onSelect}
fieldLabel={defaultProps.fieldLabel}
handleClear={defaultProps.handleClear}
/>,
);
const input = screen.getByRole('combobox');
// first clear then type
await user.clear(input);
await user.type(input, 'test');
expect(input).toHaveValue('test');
// Value prop change should not override local typing
await act(async () => {
// Simulate prop change
render(
<YAxisUnitSelector
value="bytes"
onSelect={defaultProps.onSelect}
fieldLabel={defaultProps.fieldLabel}
handleClear={defaultProps.handleClear}
/>,
);
});
// Local typing should be preserved
expect(input).toHaveValue('test');
});
});
});

View File

@ -337,7 +337,6 @@ function RightContainer({
{allowYAxisUnit && ( {allowYAxisUnit && (
<YAxisUnitSelector <YAxisUnitSelector
defaultValue={yAxisUnit}
onSelect={setYAxisUnit} onSelect={setYAxisUnit}
value={yAxisUnit || ''} value={yAxisUnit || ''}
fieldLabel={ fieldLabel={