Loki Query Variables: Add support to select from existing labels (#54625)

* feat(loki-variable-editor): replace input with select with datasource labels

* feat(loki-variable-editor): update test

* feat(loki-variable-editor): allow the editor to receive an existing query instance and edit it

* feat(loki-variable-editor): allow custom values in the label select

* feat(loki-variable-editor): mark stream field as optional

* feat(loki-variable-editor): add placeholder to stream selector and extend tooltip info
This commit is contained in:
Matias Chomicki 2022-09-09 14:59:07 +02:00 committed by GitHub
parent 7104d90b39
commit 4bed59efb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 135 additions and 38 deletions

View File

@ -10,43 +10,57 @@ import { LokiVariableQueryType } from '../types';
import { LokiVariableQueryEditor, Props } from './VariableQueryEditor';
const props: Props = {
datasource: createLokiDatasource({} as unknown as TemplateSrv),
query: {
refId: 'test',
type: LokiVariableQueryType.LabelNames,
},
onRunQuery: () => {},
onChange: () => {},
};
const refId = 'LokiVariableQueryEditor-VariableQuery';
describe('LokiVariableQueryEditor', () => {
test('Allows to create a Label names variable', async () => {
const onChange = jest.fn();
let props: Props;
render(<LokiVariableQueryEditor {...props} onChange={onChange} />);
beforeEach(() => {
props = {
datasource: createLokiDatasource({} as unknown as TemplateSrv),
query: {
refId: 'test',
type: LokiVariableQueryType.LabelNames,
},
onRunQuery: () => {},
onChange: () => {},
};
expect(onChange).not.toHaveBeenCalled();
await selectOptionInTest(screen.getByLabelText('Query type'), 'Label names');
expect(onChange).toHaveBeenCalledWith({
type: LokiVariableQueryType.LabelNames,
label: '',
stream: '',
refId: 'LokiVariableQueryEditor-VariableQuery',
});
jest.spyOn(props.datasource, 'labelNamesQuery').mockResolvedValue([]);
});
test('Allows to create a Label values variable', async () => {
test('Allows to create a Label names variable', async () => {
const onChange = jest.fn();
render(<LokiVariableQueryEditor {...props} onChange={onChange} />);
expect(onChange).not.toHaveBeenCalled();
await selectOptionInTest(screen.getByLabelText('Query type'), 'Label values');
await userEvent.type(screen.getByLabelText('Label'), 'label');
expect(onChange).toHaveBeenCalledWith({
type: LokiVariableQueryType.LabelValues,
label: '',
stream: '',
refId,
});
});
test('Allows to create a Label values variable', async () => {
const onChange = jest.fn();
jest.spyOn(props.datasource, 'labelNamesQuery').mockResolvedValue([
{
text: 'moon',
},
{
text: 'luna',
},
]);
render(<LokiVariableQueryEditor {...props} onChange={onChange} />);
expect(onChange).not.toHaveBeenCalled();
await selectOptionInTest(screen.getByLabelText('Query type'), 'Label values');
await selectOptionInTest(screen.getByLabelText('Label'), 'luna');
await userEvent.type(screen.getByLabelText('Stream selector'), 'stream');
await waitFor(() => expect(screen.getByDisplayValue('stream')).toBeInTheDocument());
@ -55,20 +69,68 @@ describe('LokiVariableQueryEditor', () => {
expect(onChange).toHaveBeenCalledWith({
type: LokiVariableQueryType.LabelValues,
label: 'label',
label: 'luna',
stream: 'stream',
refId: 'LokiVariableQueryEditor-VariableQuery',
refId,
});
});
test('Allows to create a Label values variable with custom label', async () => {
const onChange = jest.fn();
jest.spyOn(props.datasource, 'labelNamesQuery').mockResolvedValue([
{
text: 'moon',
},
{
text: 'luna',
},
]);
render(<LokiVariableQueryEditor {...props} onChange={onChange} />);
expect(onChange).not.toHaveBeenCalled();
await selectOptionInTest(screen.getByLabelText('Query type'), 'Label values');
await userEvent.type(screen.getByLabelText('Label'), 'sol{enter}');
await userEvent.type(screen.getByLabelText('Stream selector'), 'stream');
await waitFor(() => expect(screen.getByDisplayValue('stream')).toBeInTheDocument());
await userEvent.click(document.body);
expect(onChange).toHaveBeenCalledWith({
type: LokiVariableQueryType.LabelValues,
label: 'sol',
stream: 'stream',
refId,
});
});
test('Migrates legacy string queries to LokiVariableQuery instances', async () => {
const query = 'label_values(log stream selector, label)';
const query = 'label_values(log stream selector, label_selector)';
// @ts-expect-error
render(<LokiVariableQueryEditor {...props} onChange={() => {}} query={query} />);
await waitFor(() => expect(screen.getByText('Label values')).toBeInTheDocument());
await waitFor(() => expect(screen.getByDisplayValue('label')).toBeInTheDocument());
await waitFor(() => expect(screen.getByText('label_selector')).toBeInTheDocument());
await waitFor(() => expect(screen.getByDisplayValue('log stream selector')).toBeInTheDocument());
});
test('Receives a query instance and assigns its values when editing', async () => {
render(
<LokiVariableQueryEditor
{...props}
onChange={() => {}}
query={{
type: LokiVariableQueryType.LabelValues,
label: 'label_selector',
stream: 'log stream selector',
refId,
}}
/>
);
await waitFor(() => expect(screen.getByText('Label values')).toBeInTheDocument());
await waitFor(() => expect(screen.getByText('label_selector')).toBeInTheDocument());
await waitFor(() => expect(screen.getByDisplayValue('log stream selector')).toBeInTheDocument());
});
});

View File

@ -14,22 +14,39 @@ const variableOptions = [
export type Props = QueryEditorProps<LokiDatasource, LokiQuery, LokiOptions, LokiVariableQuery>;
export const LokiVariableQueryEditor: FC<Props> = ({ onChange, query }) => {
const refId = 'LokiVariableQueryEditor-VariableQuery';
export const LokiVariableQueryEditor: FC<Props> = ({ onChange, query, datasource }) => {
const [type, setType] = useState<number | undefined>(undefined);
const [label, setLabel] = useState('');
const [labelOptions, setLabelOptions] = useState<Array<SelectableValue<string>>>([]);
const [stream, setStream] = useState('');
useEffect(() => {
if (!query || typeof query !== 'string') {
if (!query) {
return;
}
const variableQuery = migrateVariableQuery(query);
const variableQuery = typeof query === 'string' ? migrateVariableQuery(query) : query;
setType(variableQuery.type);
setLabel(variableQuery.label || '');
setStream(variableQuery.stream || '');
if (variableQuery.label) {
setLabelOptions([{ label: variableQuery.label, value: variableQuery.label }]);
}
}, [query]);
useEffect(() => {
if (type !== QueryType.LabelValues) {
return;
}
datasource.labelNamesQuery().then((labelNames: Array<{ text: string }>) => {
setLabelOptions(labelNames.map(({ text }) => ({ label: text, value: text })));
});
}, [datasource, type]);
const onQueryTypeChange = (newType: SelectableValue<QueryType>) => {
setType(newType.value);
if (newType.value !== undefined) {
@ -37,13 +54,13 @@ export const LokiVariableQueryEditor: FC<Props> = ({ onChange, query }) => {
type: newType.value,
label,
stream,
refId: 'LokiVariableQueryEditor-VariableQuery',
refId,
});
}
};
const onLabelChange = (e: FormEvent<HTMLInputElement>) => {
setLabel(e.currentTarget.value);
const onLabelChange = (newLabel: SelectableValue<string>) => {
setLabel(newLabel.value || '');
};
const onStreamChange = (e: FormEvent<HTMLInputElement>) => {
@ -71,15 +88,33 @@ export const LokiVariableQueryEditor: FC<Props> = ({ onChange, query }) => {
{type === QueryType.LabelValues && (
<>
<InlineField label="Label" labelWidth={20}>
<Input type="text" aria-label="Label" value={label} onChange={onLabelChange} onBlur={handleBlur} />
<Select
aria-label="Label"
onChange={onLabelChange}
onBlur={handleBlur}
value={label}
options={labelOptions}
width={16}
allowCustomValue
/>
</InlineField>
<InlineField label="Stream selector" labelWidth={20}>
<InlineField
label="Stream selector"
labelWidth={20}
tooltip={
<div>
Optional. If defined, a list of values for the label in the specified log stream selector is returned.
</div>
}
>
<Input
type="text"
aria-label="Stream selector"
placeholder="Optional stream selector"
value={stream}
onChange={onStreamChange}
onBlur={handleBlur}
width={22}
/>
</InlineField>
</>