Loki Query Builder: ensure unique ids for labelled fields (#74398)

* Loki Query Builder: ensure unique ids for labelled fields

* Rename refactored argument
This commit is contained in:
Matias Chomicki 2023-09-06 10:25:53 +02:00 committed by GitHub
parent c3cbe220bb
commit 9310bb632e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 26 additions and 16 deletions

View File

@ -63,7 +63,7 @@ const createProps = (
onChange: jest.fn(), onChange: jest.fn(),
onRunQuery: jest.fn(), onRunQuery: jest.fn(),
index: 1, index: 1,
operationIndex: 1, operationId: '1',
query: { query: {
labels: [{ op: '=', label: 'foo', value: 'bar' }], labels: [{ op: '=', label: 'foo', value: 'bar' }],
operations: [ operations: [

View File

@ -15,7 +15,7 @@ import { LokiVisualQuery } from '../types';
export function UnwrapParamEditor({ export function UnwrapParamEditor({
onChange, onChange,
index, index,
operationIndex, operationId,
value, value,
query, query,
datasource, datasource,
@ -27,7 +27,7 @@ export function UnwrapParamEditor({
return ( return (
<Select <Select
inputId={getOperationParamId(operationIndex, index)} inputId={getOperationParamId(operationId, index)}
onOpenMenu={async () => { onOpenMenu={async () => {
// This check is always true, we do it to make typescript happy // This check is always true, we do it to make typescript happy
if (datasource instanceof LokiDatasource) { if (datasource instanceof LokiDatasource) {

View File

@ -11,7 +11,7 @@ import { PromVisualQuery } from '../types';
export function LabelParamEditor({ export function LabelParamEditor({
onChange, onChange,
index, index,
operationIndex, operationId,
value, value,
query, query,
datasource, datasource,
@ -23,7 +23,7 @@ export function LabelParamEditor({
return ( return (
<Select <Select
inputId={getOperationParamId(operationIndex, index)} inputId={getOperationParamId(operationId, index)}
autoFocus={value === '' ? true : undefined} autoFocus={value === '' ? true : undefined}
openMenuOnFocus openMenuOnFocus
onOpenMenu={async () => { onOpenMenu={async () => {

View File

@ -30,7 +30,7 @@ describe('PromQueryBuilderContainer', () => {
await userEvent.click(screen.getByTestId('operations.0.add-rest-param')); await userEvent.click(screen.getByTestId('operations.0.add-rest-param'));
waitFor(() => { waitFor(() => {
expect(container.querySelector(`${getOperationParamId(0, 0)}`)).toBeInTheDocument(); expect(container.querySelector(`${getOperationParamId('0', 0)}`)).toBeInTheDocument();
}); });
}); });
}); });

View File

@ -1,5 +1,5 @@
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useId, useState } from 'react';
import { Draggable } from 'react-beautiful-dnd'; import { Draggable } from 'react-beautiful-dnd';
import { DataSourceApi, GrafanaTheme2 } from '@grafana/data'; import { DataSourceApi, GrafanaTheme2 } from '@grafana/data';
@ -46,6 +46,7 @@ export function OperationEditor({
}: Props) { }: Props) {
const def = queryModeller.getOperationDef(operation.id); const def = queryModeller.getOperationDef(operation.id);
const shouldFlash = useFlash(flash); const shouldFlash = useFlash(flash);
const id = useId();
const isConflicting = const isConflicting =
operation.id === LokiOperationId.LabelFilter && isConflictingFilter(operation, query.operations); operation.id === LokiOperationId.LabelFilter && isConflictingFilter(operation, query.operations);
@ -86,7 +87,7 @@ export function OperationEditor({
<div className={styles.paramRow} key={`${paramIndex}-1`}> <div className={styles.paramRow} key={`${paramIndex}-1`}>
{!paramDef.hideName && ( {!paramDef.hideName && (
<div className={styles.paramName}> <div className={styles.paramName}>
<label htmlFor={getOperationParamId(index, paramIndex)}>{paramDef.name}</label> <label htmlFor={getOperationParamId(id, paramIndex)}>{paramDef.name}</label>
{paramDef.description && ( {paramDef.description && (
<Tooltip placement="top" content={paramDef.description} theme="info"> <Tooltip placement="top" content={paramDef.description} theme="info">
<Icon name="info-circle" size="sm" className={styles.infoIcon} /> <Icon name="info-circle" size="sm" className={styles.infoIcon} />
@ -101,7 +102,7 @@ export function OperationEditor({
paramDef={paramDef} paramDef={paramDef}
value={operation.params[paramIndex]} value={operation.params[paramIndex]}
operation={operation} operation={operation}
operationIndex={index} operationId={id}
onChange={onParamValueChanged} onChange={onParamValueChanged}
onRunQuery={onRunQuery} onRunQuery={onRunQuery}
query={query} query={query}

View File

@ -33,7 +33,7 @@ export function getOperationParamEditor(
function SimpleInputParamEditor(props: QueryBuilderOperationParamEditorProps) { function SimpleInputParamEditor(props: QueryBuilderOperationParamEditorProps) {
return ( return (
<AutoSizeInput <AutoSizeInput
id={getOperationParamId(props.operationIndex, props.index)} id={getOperationParamId(props.operationId, props.index)}
defaultValue={props.value?.toString()} defaultValue={props.value?.toString()}
minWidth={props.paramDef.minWidth} minWidth={props.paramDef.minWidth}
placeholder={props.paramDef.placeholder} placeholder={props.paramDef.placeholder}
@ -52,7 +52,7 @@ function SimpleInputParamEditor(props: QueryBuilderOperationParamEditorProps) {
function BoolInputParamEditor(props: QueryBuilderOperationParamEditorProps) { function BoolInputParamEditor(props: QueryBuilderOperationParamEditorProps) {
return ( return (
<Checkbox <Checkbox
id={getOperationParamId(props.operationIndex, props.index)} id={getOperationParamId(props.operationId, props.index)}
value={props.value as boolean} value={props.value as boolean}
onChange={(evt) => props.onChange(props.index, evt.currentTarget.checked)} onChange={(evt) => props.onChange(props.index, evt.currentTarget.checked)}
/> />
@ -63,7 +63,7 @@ function SelectInputParamEditor({
paramDef, paramDef,
value, value,
index, index,
operationIndex, operationId,
onChange, onChange,
}: QueryBuilderOperationParamEditorProps) { }: QueryBuilderOperationParamEditorProps) {
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
@ -99,7 +99,7 @@ function SelectInputParamEditor({
return ( return (
<Stack gap={0.5} direction="row" alignItems="center" wrap={false}> <Stack gap={0.5} direction="row" alignItems="center" wrap={false}>
<Select <Select
id={getOperationParamId(operationIndex, index)} id={getOperationParamId(operationId, index)}
value={valueOption} value={valueOption}
options={selectOptions} options={selectOptions}
placeholder={paramDef.placeholder} placeholder={paramDef.placeholder}

View File

@ -1,6 +1,7 @@
import { import {
createAggregationOperation, createAggregationOperation,
createAggregationOperationWithParam, createAggregationOperationWithParam,
getOperationParamId,
isConflictingSelector, isConflictingSelector,
} from './operationUtils'; } from './operationUtils';
@ -197,3 +198,11 @@ describe('isConflictingSelector', () => {
expect(isConflictingSelector(newLabel, labels)).toBe(false); expect(isConflictingSelector(newLabel, labels)).toBe(false);
}); });
}); });
describe('getOperationParamId', () => {
it('Generates correct id for operation param', () => {
const operationId = 'abc';
const paramId = 0;
expect(getOperationParamId(operationId, paramId)).toBe('operations.abc.param.0');
});
});

View File

@ -120,8 +120,8 @@ export function getPromAndLokiOperationDisplayName(funcName: string) {
return capitalize(funcName.replace(/_/g, ' ')); return capitalize(funcName.replace(/_/g, ' '));
} }
export function getOperationParamId(operationIndex: number, paramIndex: number) { export function getOperationParamId(operationId: string, paramIndex: number) {
return `operations.${operationIndex}.param.${paramIndex}`; return `operations.${operationId}.param.${paramIndex}`;
} }
export function getRangeVectorParamDef(withRateInterval = false): QueryBuilderOperationParamDef { export function getRangeVectorParamDef(withRateInterval = false): QueryBuilderOperationParamDef {

View File

@ -89,7 +89,7 @@ export interface QueryBuilderOperationParamEditorProps {
/** Parameter index */ /** Parameter index */
index: number; index: number;
operation: QueryBuilderOperation; operation: QueryBuilderOperation;
operationIndex: number; operationId: string;
query: any; query: any;
datasource: DataSourceApi; datasource: DataSourceApi;
onChange: (index: number, value: QueryBuilderOperationParamValue) => void; onChange: (index: number, value: QueryBuilderOperationParamValue) => void;