FilterByValueTransformer: Suggest variable regex formatter (#105272)
--------- Co-authored-by: Sam Jewell <sam.jewell@grafana.com>
This commit is contained in:
co-authored by
Sam Jewell
parent
a30b1c4bee
commit
aa72d800a1
+58
@@ -0,0 +1,58 @@
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
|
||||
import { FieldType, TypedVariableModel } from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import { basicMatcherEditor } from './BasicMatcherEditor';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
getTemplateSrv: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('BasicMatcherEditor', () => {
|
||||
it('shows variable suggestions', async () => {
|
||||
// Mock template service variables
|
||||
const mockVariables: TypedVariableModel[] = [
|
||||
{ name: 'var1', label: 'Variable 1', type: 'custom' } as TypedVariableModel,
|
||||
{ name: 'var2', label: 'Variable 2', type: 'custom' } as TypedVariableModel,
|
||||
];
|
||||
|
||||
const mockTemplateSrv = {
|
||||
getVariables: () => mockVariables,
|
||||
replace: jest.fn(),
|
||||
containsTemplate: jest.fn(),
|
||||
updateTimeRange: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mocked(getTemplateSrv).mockReturnValue(mockTemplateSrv);
|
||||
|
||||
const onChangeMock = jest.fn();
|
||||
const options = { value: '' };
|
||||
const field = {
|
||||
name: 'test',
|
||||
type: FieldType.string,
|
||||
config: {},
|
||||
values: [],
|
||||
};
|
||||
|
||||
const Editor = basicMatcherEditor({ validator: () => true });
|
||||
render(<Editor options={options} onChange={onChangeMock} field={field} />);
|
||||
|
||||
// Focus the input and press $ to trigger suggestions
|
||||
const input = screen.getByPlaceholderText('Value or variable');
|
||||
fireEvent.focus(input);
|
||||
fireEvent.keyDown(input, { key: '$' });
|
||||
|
||||
// Wait for suggestions to appear and verify
|
||||
await waitFor(() => {
|
||||
const suggestions = screen.getAllByRole('menuitem');
|
||||
// Verify exact number of suggestions
|
||||
expect(suggestions).toHaveLength(mockVariables.length);
|
||||
|
||||
mockVariables.forEach((variable) => {
|
||||
const suggestion = suggestions.find((s) => s.textContent?.includes(variable.label as string));
|
||||
expect(suggestion).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { NoopMatcherEditor } from './NoopMatcherEditor';
|
||||
|
||||
describe('NoopMatcherEditor', () => {
|
||||
it('renders nothing', () => {
|
||||
const { container } = render(<NoopMatcherEditor />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
|
||||
import { FieldType, TypedVariableModel } from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import { rangeMatcherEditor } from './RangeMatcherEditor';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
getTemplateSrv: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('RangeMatcherEditor', () => {
|
||||
it('shows variable suggestions for both from and to inputs', async () => {
|
||||
// Mock template service variables
|
||||
const mockVariables: TypedVariableModel[] = [
|
||||
{ name: 'var1', label: 'Variable 1', type: 'custom' } as TypedVariableModel,
|
||||
{ name: 'var2', label: 'Variable 2', type: 'custom' } as TypedVariableModel,
|
||||
];
|
||||
|
||||
const mockTemplateSrv = {
|
||||
getVariables: () => mockVariables,
|
||||
replace: jest.fn(),
|
||||
containsTemplate: jest.fn(),
|
||||
updateTimeRange: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mocked(getTemplateSrv).mockReturnValue(mockTemplateSrv);
|
||||
|
||||
const onChangeMock = jest.fn();
|
||||
const options = { from: '', to: '' };
|
||||
const field = {
|
||||
name: 'test',
|
||||
type: FieldType.string,
|
||||
config: {},
|
||||
values: [],
|
||||
};
|
||||
|
||||
const Editor = rangeMatcherEditor({ validator: () => true });
|
||||
render(<Editor options={options} onChange={onChangeMock} field={field} />);
|
||||
|
||||
// Test "from" input
|
||||
const fromInput = screen.getByPlaceholderText('From');
|
||||
fireEvent.focus(fromInput);
|
||||
fireEvent.keyDown(fromInput, { key: '$' });
|
||||
|
||||
// Wait for suggestions to appear and verify for "from" input
|
||||
await waitFor(() => {
|
||||
const menus = screen.getAllByRole('menu');
|
||||
const fromMenu = menus[0]; // First menu is for the "from" input
|
||||
const fromSuggestions = fromMenu.querySelectorAll('[role="menuitem"]');
|
||||
// Verify exact number of suggestions for "from" input
|
||||
expect(fromSuggestions).toHaveLength(mockVariables.length);
|
||||
|
||||
mockVariables.forEach((variable) => {
|
||||
const suggestion = Array.from(fromSuggestions).find((s) => s.textContent?.includes(variable.label as string));
|
||||
expect(suggestion).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
// Clear suggestions
|
||||
fireEvent.blur(fromInput);
|
||||
|
||||
// Test "to" input
|
||||
const toInput = screen.getByPlaceholderText('To');
|
||||
fireEvent.focus(toInput);
|
||||
fireEvent.keyDown(toInput, { key: '$' });
|
||||
|
||||
// Wait for suggestions to appear and verify for "to" input
|
||||
await waitFor(() => {
|
||||
const menus = screen.getAllByRole('menu');
|
||||
const toMenu = menus[1];
|
||||
const toSuggestions = toMenu.querySelectorAll('[role="menuitem"]');
|
||||
// Verify exact number of suggestions for "to" input
|
||||
expect(toSuggestions).toHaveLength(mockVariables.length);
|
||||
|
||||
mockVariables.forEach((variable) => {
|
||||
const suggestion = Array.from(toSuggestions).find((s) => s.textContent?.includes(variable.label as string));
|
||||
expect(suggestion).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
|
||||
import { FieldType, TypedVariableModel } from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import { regexMatcherEditor } from './RegexMatcherEditor';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
getTemplateSrv: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('RegexMatcherEditor', () => {
|
||||
it('adds :regex suffix to variable suggestions', async () => {
|
||||
// Mock template service variables
|
||||
const mockVariables: TypedVariableModel[] = [
|
||||
{ name: 'var1', label: 'Variable 1', type: 'custom' } as TypedVariableModel,
|
||||
{ name: 'var2', label: 'Variable 2', type: 'custom' } as TypedVariableModel,
|
||||
];
|
||||
|
||||
const mockTemplateSrv = {
|
||||
getVariables: () => mockVariables,
|
||||
replace: jest.fn(),
|
||||
containsTemplate: jest.fn(),
|
||||
updateTimeRange: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mocked(getTemplateSrv).mockReturnValue(mockTemplateSrv);
|
||||
|
||||
const onChangeMock = jest.fn();
|
||||
const options = { value: '' };
|
||||
const field = {
|
||||
name: 'test',
|
||||
type: FieldType.string,
|
||||
config: {},
|
||||
values: [],
|
||||
};
|
||||
|
||||
const Editor = regexMatcherEditor({ validator: () => true });
|
||||
render(<Editor options={options} onChange={onChangeMock} field={field} />);
|
||||
|
||||
// Focus the input and press $ to trigger suggestions
|
||||
const input = screen.getByPlaceholderText('Value or variable');
|
||||
fireEvent.focus(input);
|
||||
fireEvent.keyDown(input, { key: '$' });
|
||||
|
||||
// Wait for suggestions to appear and verify
|
||||
await waitFor(() => {
|
||||
const suggestions = screen.getAllByRole('menuitem');
|
||||
// Verify exact number of suggestions (each variable has both regular and regex versions)
|
||||
expect(suggestions).toHaveLength(mockVariables.length * 2);
|
||||
|
||||
mockVariables.forEach((variable) => {
|
||||
const regularSuggestion = suggestions.find((s) => s.textContent?.includes(variable.label as string));
|
||||
const regexSuggestion = suggestions.find((s) => s.textContent?.includes(`${variable.label as string}:regex`));
|
||||
|
||||
expect(regularSuggestion).toBeInTheDocument();
|
||||
expect(regexSuggestion).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+15
-2
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { ValueMatcherID, BasicValueMatcherOptions } from '@grafana/data';
|
||||
import { ValueMatcherID, BasicValueMatcherOptions, VariableSuggestion } from '@grafana/data';
|
||||
import { t } from 'app/core/internationalization';
|
||||
|
||||
import { SuggestionsInput } from '../../suggestionsInput/SuggestionsInput';
|
||||
@@ -16,6 +16,19 @@ export function regexMatcherEditor(
|
||||
const { validator } = config;
|
||||
const { value } = options;
|
||||
const [isInvalid, setInvalid] = useState(!validator(value));
|
||||
const variableSuggestions = getVariableSuggestions().reduce<VariableSuggestion[]>((acc, v) => {
|
||||
acc.push(v);
|
||||
acc.push({
|
||||
...v,
|
||||
documentation: t(
|
||||
'transformers.regex-matcher-editor.variable-regex-documentation',
|
||||
'Formats multi-value variable into a regex string'
|
||||
),
|
||||
label: v.label.concat(':regex'),
|
||||
value: v.value.concat(':regex'),
|
||||
});
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const onChangeVariableValue = useCallback(
|
||||
(value: string) => {
|
||||
@@ -34,7 +47,7 @@ export function regexMatcherEditor(
|
||||
value={value}
|
||||
onChange={onChangeVariableValue}
|
||||
placeholder={t('transformers.regex-matcher-editor.placeholder-value-or-variable', 'Value or variable')}
|
||||
suggestions={getVariableSuggestions()}
|
||||
suggestions={variableSuggestions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9019,7 +9019,8 @@
|
||||
"placeholder-choose-stat": "Choose stat"
|
||||
},
|
||||
"regex-matcher-editor": {
|
||||
"placeholder-value-or-variable": "Value or variable"
|
||||
"placeholder-value-or-variable": "Value or variable",
|
||||
"variable-regex-documentation": "Formats multi-value variable into a regex string"
|
||||
},
|
||||
"regression-transformer-editor": {
|
||||
"label-degree": "Degree",
|
||||
|
||||
Reference in New Issue
Block a user