Tempo: Select performance improvements (#91732)

* Tempo select performance improvements

* Update type

* Tidy up and simplify

* Update tagValueOptions

* Update GroupBy options
pull/91845/head
Joey 11 months ago committed by GitHub
parent d72846790e
commit d779dfb0a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 32
      public/app/plugins/datasource/tempo/SearchTraceQLEditor/GroupByField.tsx
  2. 71
      public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.tsx

@ -1,17 +1,17 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { useEffect } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { AccessoryButton } from '@grafana/experimental'; import { AccessoryButton } from '@grafana/experimental';
import { HorizontalGroup, Select, useStyles2 } from '@grafana/ui'; import { HorizontalGroup, InputActionMeta, Select, useStyles2 } from '@grafana/ui';
import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen'; import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen';
import { TempoDatasource } from '../datasource'; import { TempoDatasource } from '../datasource';
import { TempoQuery } from '../types'; import { TempoQuery } from '../types';
import InlineSearchField from './InlineSearchField'; import InlineSearchField from './InlineSearchField';
import { withTemplateVariableOptions } from './SearchField'; import { maxOptions, withTemplateVariableOptions } from './SearchField';
import { replaceAt } from './utils'; import { replaceAt } from './utils';
interface Props { interface Props {
@ -26,6 +26,7 @@ export const GroupByField = (props: Props) => {
const { datasource, onChange, query, isTagsLoading, addVariablesToOptions } = props; const { datasource, onChange, query, isTagsLoading, addVariablesToOptions } = props;
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const generateId = () => uuidv4().slice(0, 8); const generateId = () => uuidv4().slice(0, 8);
const [tagQuery, setTagQuery] = useState<string>('');
useEffect(() => { useEffect(() => {
if (!query.groupBy || query.groupBy.length === 0) { if (!query.groupBy || query.groupBy.length === 0) {
@ -41,9 +42,18 @@ export const GroupByField = (props: Props) => {
} }
}, [onChange, query]); }, [onChange, query]);
const getTags = (f: TraceqlFilter) => { const tagOptions = useMemo(
return datasource!.languageProvider.getMetricsSummaryTags(f.scope); () => (f: TraceqlFilter) => {
}; const tags = datasource!.languageProvider.getMetricsSummaryTags(f.scope);
if (tagQuery.length === 0) {
return tags.slice(0, maxOptions);
}
const queryLowerCase = tagQuery.toLowerCase();
return tags.filter((tag) => tag.toLowerCase().includes(queryLowerCase)).slice(0, maxOptions);
},
[datasource, tagQuery]
);
const addFilter = () => { const addFilter = () => {
updateFilter({ updateFilter({
@ -74,8 +84,8 @@ export const GroupByField = (props: Props) => {
<InlineSearchField label="Aggregate by" tooltip="Select one or more tags to see the metrics summary."> <InlineSearchField label="Aggregate by" tooltip="Select one or more tags to see the metrics summary.">
<> <>
{query.groupBy?.map((f, i) => { {query.groupBy?.map((f, i) => {
const tags = getTags(f) const tags = tagOptions(f)
?.concat(f.tag !== undefined && !getTags(f)?.includes(f.tag) ? [f.tag] : []) ?.concat(f.tag !== undefined && !tagOptions(f)?.includes(f.tag) ? [f.tag] : [])
.map((t) => ({ .map((t) => ({
label: t, label: t,
value: t, value: t,
@ -102,6 +112,12 @@ export const GroupByField = (props: Props) => {
updateFilter({ ...f, tag: v?.value }); updateFilter({ ...f, tag: v?.value });
}} }}
options={addVariablesToOptions ? withTemplateVariableOptions(tags) : tags} options={addVariablesToOptions ? withTemplateVariableOptions(tags) : tags}
onInputChange={(value: string, { action }: InputActionMeta) => {
if (action === 'input-change') {
setTagQuery(value);
}
}}
onCloseMenu={() => setTagQuery('')}
placeholder="Select tag" placeholder="Select tag"
value={f.tag || ''} value={f.tag || ''}
/> />

@ -6,7 +6,7 @@ import useAsync from 'react-use/lib/useAsync';
import { SelectableValue } from '@grafana/data'; import { SelectableValue } from '@grafana/data';
import { TemporaryAlert } from '@grafana/o11y-ds-frontend'; import { TemporaryAlert } from '@grafana/o11y-ds-frontend';
import { FetchError, getTemplateSrv, isFetchError } from '@grafana/runtime'; import { FetchError, getTemplateSrv, isFetchError } from '@grafana/runtime';
import { Select, HorizontalGroup, useStyles2 } from '@grafana/ui'; import { Select, HorizontalGroup, useStyles2, InputActionMeta } from '@grafana/ui';
import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen'; import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen';
import { TempoDatasource } from '../datasource'; import { TempoDatasource } from '../datasource';
@ -59,6 +59,8 @@ const SearchField = ({
// there's only one value selected, so we store the previous operator and value // there's only one value selected, so we store the previous operator and value
const [prevOperator, setPrevOperator] = useState(filter.operator); const [prevOperator, setPrevOperator] = useState(filter.operator);
const [prevValue, setPrevValue] = useState(filter.value); const [prevValue, setPrevValue] = useState(filter.value);
const [tagQuery, setTagQuery] = useState<string>('');
const [tagValuesQuery, setTagValuesQuery] = useState<string>('');
const updateOptions = async () => { const updateOptions = async () => {
try { try {
@ -127,13 +129,41 @@ const SearchField = ({
case 'float': case 'float':
operatorList = numberOperators; operatorList = numberOperators;
} }
const operatorOptions = operatorList.map(operatorSelectableValue);
const tagOptions = (filter.tag !== undefined ? uniq([filter.tag, ...tags]) : tags).map((t) => ({ const formatTagOptions = (tags: string[], filterTag: string | undefined) => {
label: t, return (filterTag !== undefined ? uniq([filterTag, ...tags]) : tags).map((t) => ({ label: t, value: t }));
value: t, };
}));
const operatorOptions = operatorList.map(operatorSelectableValue); const tagOptions = useMemo(() => {
if (tagQuery.length === 0) {
return formatTagOptions(tags.slice(0, maxOptions), filter.tag);
}
const queryLowerCase = tagQuery.toLowerCase();
const filterdOptions = tags.filter((tag) => tag.toLowerCase().includes(queryLowerCase)).slice(0, maxOptions);
return formatTagOptions(filterdOptions, filter.tag);
}, [filter.tag, tagQuery, tags]);
const tagValueOptions = useMemo(() => {
if (!options) {
return;
}
if (tagValuesQuery.length === 0) {
return options.slice(0, maxOptions);
}
const queryLowerCase = tagValuesQuery.toLowerCase();
return options
.filter((tag) => {
if (tag.value && tag.value.length > 0) {
return tag.value.toLowerCase().includes(queryLowerCase);
}
return false;
})
.slice(0, maxOptions);
}, [tagValuesQuery, options]);
return ( return (
<> <>
@ -144,9 +174,7 @@ const SearchField = ({
inputId={`${filter.id}-scope`} inputId={`${filter.id}-scope`}
options={addVariablesToOptions ? withTemplateVariableOptions(scopeOptions) : scopeOptions} options={addVariablesToOptions ? withTemplateVariableOptions(scopeOptions) : scopeOptions}
value={filter.scope} value={filter.scope}
onChange={(v) => { onChange={(v) => updateFilter({ ...filter, scope: v?.value })}
updateFilter({ ...filter, scope: v?.value });
}}
placeholder="Select scope" placeholder="Select scope"
aria-label={`select ${filter.id} scope`} aria-label={`select ${filter.id} scope`}
/> />
@ -158,10 +186,14 @@ const SearchField = ({
isLoading={isTagsLoading} isLoading={isTagsLoading}
// Add the current tag to the list if it doesn't exist in the tags prop, otherwise the field will be empty even though the state has a value // Add the current tag to the list if it doesn't exist in the tags prop, otherwise the field will be empty even though the state has a value
options={addVariablesToOptions ? withTemplateVariableOptions(tagOptions) : tagOptions} options={addVariablesToOptions ? withTemplateVariableOptions(tagOptions) : tagOptions}
value={filter.tag} onInputChange={(value: string, { action }: InputActionMeta) => {
onChange={(v) => { if (action === 'input-change') {
updateFilter({ ...filter, tag: v?.value, value: [] }); setTagQuery(value);
}
}} }}
onCloseMenu={() => setTagQuery('')}
onChange={(v) => updateFilter({ ...filter, tag: v?.value, value: [] })}
value={filter.tag}
placeholder="Select tag" placeholder="Select tag"
isClearable isClearable
aria-label={`select ${filter.id} tag`} aria-label={`select ${filter.id} tag`}
@ -174,9 +206,7 @@ const SearchField = ({
inputId={`${filter.id}-operator`} inputId={`${filter.id}-operator`}
options={addVariablesToOptions ? withTemplateVariableOptions(operatorOptions) : operatorOptions} options={addVariablesToOptions ? withTemplateVariableOptions(operatorOptions) : operatorOptions}
value={filter.operator} value={filter.operator}
onChange={(v) => { onChange={(v) => updateFilter({ ...filter, operator: v?.value })}
updateFilter({ ...filter, operator: v?.value });
}}
isClearable={false} isClearable={false}
aria-label={`select ${filter.id} operator`} aria-label={`select ${filter.id} operator`}
allowCustomValue={true} allowCustomValue={true}
@ -193,8 +223,14 @@ const SearchField = ({
className={styles.dropdown} className={styles.dropdown}
inputId={`${filter.id}-value`} inputId={`${filter.id}-value`}
isLoading={isLoadingValues} isLoading={isLoadingValues}
options={addVariablesToOptions ? withTemplateVariableOptions(options) : options} options={addVariablesToOptions ? withTemplateVariableOptions(tagValueOptions) : tagValueOptions}
value={filter.value} value={filter.value}
onInputChange={(value: string, { action }: InputActionMeta) => {
if (action === 'input-change') {
setTagValuesQuery(value);
}
}}
onCloseMenu={() => setTagValuesQuery('')}
onChange={(val) => { onChange={(val) => {
if (Array.isArray(val)) { if (Array.isArray(val)) {
updateFilter({ updateFilter({
@ -231,4 +267,7 @@ export const withTemplateVariableOptions = (options: SelectableValue[] | undefin
return [...(options || []), ...templateVariables.map((v) => ({ label: `$${v.name}`, value: `$${v.name}` }))]; return [...(options || []), ...templateVariables.map((v) => ({ label: `$${v.name}`, value: `$${v.name}` }))];
}; };
// Limit maximum options in select dropdowns for performance reasons
export const maxOptions = 10000;
export default SearchField; export default SearchField;

Loading…
Cancel
Save