diff --git a/.betterer.results b/.betterer.results index e2f3dade903..734158ebf57 100644 --- a/.betterer.results +++ b/.betterer.results @@ -414,9 +414,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "23"], [0, 0, 0, "Unexpected any. Specify a different type.", "24"], [0, 0, 0, "Unexpected any. Specify a different type.", "25"], - [0, 0, 0, "Unexpected any. Specify a different type.", "26"], - [0, 0, 0, "Unexpected any. Specify a different type.", "27"], - [0, 0, 0, "Unexpected any. Specify a different type.", "28"] + [0, 0, 0, "Unexpected any. Specify a different type.", "26"] ], "packages/grafana-data/src/types/explore.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -2869,17 +2867,6 @@ exports[`better eslint`] = { "public/app/features/variables/adhoc/actions.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/variables/adhoc/picker/AdHocFilter.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], - "public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], - "public/app/features/variables/adhoc/picker/AdHocFilterKey.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"] - ], "public/app/features/variables/adhoc/picker/AdHocFilterRenderer.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -3956,9 +3943,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "25"], [0, 0, 0, "Unexpected any. Specify a different type.", "26"], [0, 0, 0, "Unexpected any. Specify a different type.", "27"], - [0, 0, 0, "Unexpected any. Specify a different type.", "28"], - [0, 0, 0, "Unexpected any. Specify a different type.", "29"], - [0, 0, 0, "Unexpected any. Specify a different type.", "30"] + [0, 0, 0, "Unexpected any. Specify a different type.", "28"] ], "public/app/plugins/datasource/prometheus/language_provider.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index bbca8f57bb8..c724f2e0021 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -14,7 +14,7 @@ import { DataQuery } from './query'; import { RawTimeRange, TimeRange } from './time'; import { CustomVariableSupport, DataSourceVariableSupport, StandardVariableSupport } from './variables'; -import { DataSourceRef, WithAccessControlMetadata } from '.'; +import { AdHocVariableFilter, DataSourceRef, WithAccessControlMetadata } from '.'; export interface DataSourcePluginOptionsEditorProps< JSONData extends DataSourceJsonData = DataSourceJsonData, @@ -287,12 +287,12 @@ abstract class DataSourceApi< /** * Get tag keys for adhoc filters */ - getTagKeys?(options?: any): Promise; + getTagKeys?(options?: DataSourceGetTagKeysOptions): Promise; /** * Get tag values for adhoc filters */ - getTagValues?(options: any): Promise; + getTagValues?(options: DataSourceGetTagValuesOptions): Promise; /** * Set after constructor call, as the data source instance is the most common thing to pass around @@ -370,6 +370,35 @@ abstract class DataSourceApi< getDefaultQuery?(app: CoreApp): Partial; } +/** + * Options argument to DataSourceAPI.getTagKeys + */ +export interface DataSourceGetTagKeysOptions { + /** + * The other existing filters or base filters. New in v10.3 + */ + filters: AdHocVariableFilter[]; + /** + * Context time range. New in v10.3 + */ + timeRange?: TimeRange; +} + +/** + * Options argument to DataSourceAPI.getTagValues + */ +export interface DataSourceGetTagValuesOptions { + key: string; + /** + * The other existing filters or base filters. New in v10.3 + */ + filters: AdHocVariableFilter[]; + /** + * Context time range. New in v10.3 + */ + timeRange?: TimeRange; +} + export interface MetadataInspectorProps< DSType extends DataSourceApi, TQuery extends DataQuery = DataQuery, diff --git a/packages/grafana-data/src/types/templateVars.ts b/packages/grafana-data/src/types/templateVars.ts index 96ecef01e54..21599065bd5 100644 --- a/packages/grafana-data/src/types/templateVars.ts +++ b/packages/grafana-data/src/types/templateVars.ts @@ -55,6 +55,10 @@ export interface AdHocVariableModel extends BaseVariableModel { type: 'adhoc'; datasource: DataSourceRef | null; filters: AdHocVariableFilter[]; + /** + * Filters that are always applied to the lookup of keys. Not shown in the AdhocFilterBuilder UI. + */ + baseFilters?: AdHocVariableFilter[]; } export interface VariableOption { diff --git a/public/app/features/variables/adhoc/picker/AdHocFilter.tsx b/public/app/features/variables/adhoc/picker/AdHocFilter.tsx index f0a7bd8695e..80d8797b74e 100644 --- a/public/app/features/variables/adhoc/picker/AdHocFilter.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocFilter.tsx @@ -12,12 +12,10 @@ import { ConditionSegment } from './ConditionSegment'; interface Props { datasource: DataSourceRef | null; filters: AdHocVariableFilter[]; + baseFilters?: AdHocVariableFilter[]; addFilter: (filter: AdHocVariableFilter) => void; removeFilter: (index: number) => void; changeFilter: (index: number, newFilter: AdHocVariableFilter) => void; - // Passes options to the datasources getTagKeys(options?: any) method - // which is called to fetch the available filter key options in AdHocFilterKey.tsx - getTagKeysOptions?: any; disabled?: boolean; } @@ -60,13 +58,21 @@ export class AdHocFilter extends PureComponent { datasource={this.props.datasource!} appendBefore={filters.length > 0 ? : null} onCompleted={this.appendFilterToVariable} - getTagKeysOptions={this.props.getTagKeysOptions} + allFilters={this.getAllFilters()} /> )} ); } + getAllFilters() { + if (this.props.baseFilters) { + return this.props.baseFilters.concat(this.props.filters); + } + + return this.props.filters; + } + renderFilters(filters: AdHocVariableFilter[], disabled?: boolean) { if (filters.length === 0 && disabled) { return {}} />; @@ -91,7 +97,7 @@ export class AdHocFilter extends PureComponent { onKeyChange={this.onChange(index, 'key')} onOperatorChange={this.onChange(index, 'operator')} onValueChange={this.onChange(index, 'value')} - getTagKeysOptions={this.props.getTagKeysOptions} + allFilters={this.getAllFilters()} /> ); diff --git a/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx b/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx index 1d172ca728b..280dd839ac8 100644 --- a/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx @@ -11,10 +11,10 @@ interface Props { datasource: DataSourceRef; onCompleted: (filter: AdHocVariableFilter) => void; appendBefore?: React.ReactNode; - getTagKeysOptions?: any; + allFilters: AdHocVariableFilter[]; } -export const AdHocFilterBuilder = ({ datasource, appendBefore, onCompleted, getTagKeysOptions }: Props) => { +export const AdHocFilterBuilder = ({ datasource, appendBefore, onCompleted, allFilters }: Props) => { const [key, setKey] = useState(null); const [operator, setOperator] = useState('='); @@ -49,14 +49,7 @@ export const AdHocFilterBuilder = ({ datasource, appendBefore, onCompleted, getT ); if (key === null) { - return ( - - ); + return ; } return ( @@ -69,7 +62,7 @@ export const AdHocFilterBuilder = ({ datasource, appendBefore, onCompleted, getT onKeyChange={onKeyChanged} onOperatorChange={onOperatorChanged} onValueChange={onValueChanged} - getTagKeysOptions={getTagKeysOptions} + allFilters={allFilters} /> ); diff --git a/public/app/features/variables/adhoc/picker/AdHocFilterKey.tsx b/public/app/features/variables/adhoc/picker/AdHocFilterKey.tsx index 051b34c6f10..ca19658b1f1 100644 --- a/public/app/features/variables/adhoc/picker/AdHocFilterKey.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocFilterKey.tsx @@ -1,6 +1,6 @@ import React, { ReactElement } from 'react'; -import { DataSourceRef, SelectableValue } from '@grafana/data'; +import { AdHocVariableFilter, DataSourceRef, SelectableValue } from '@grafana/data'; import { Icon, SegmentAsync } from '@grafana/ui'; import { getDatasourceSrv } from '../../../plugins/datasource_srv'; @@ -9,14 +9,14 @@ interface Props { datasource: DataSourceRef; filterKey: string | null; onChange: (item: SelectableValue) => void; - getTagKeysOptions?: any; + allFilters: AdHocVariableFilter[]; disabled?: boolean; } const MIN_WIDTH = 90; -export const AdHocFilterKey = ({ datasource, onChange, disabled, filterKey, getTagKeysOptions }: Props) => { - const loadKeys = () => fetchFilterKeys(datasource, getTagKeysOptions); - const loadKeysWithRemove = () => fetchFilterKeysWithRemove(datasource, getTagKeysOptions); +export const AdHocFilterKey = ({ datasource, onChange, disabled, filterKey, allFilters }: Props) => { + const loadKeys = () => fetchFilterKeys(datasource, filterKey, allFilters); + const loadKeysWithRemove = () => fetchFilterKeysWithRemove(datasource, filterKey, allFilters); if (filterKey === null) { return ( @@ -59,7 +59,8 @@ const plusSegment: ReactElement = ( const fetchFilterKeys = async ( datasource: DataSourceRef, - getTagKeysOptions?: any + currentKey: string | null, + allFilters: AdHocVariableFilter[] ): Promise>> => { const ds = await getDatasourceSrv().get(datasource); @@ -67,14 +68,16 @@ const fetchFilterKeys = async ( return []; } - const metrics = await ds.getTagKeys(getTagKeysOptions); + const otherFilters = allFilters.filter((f) => f.key !== currentKey); + const metrics = await ds.getTagKeys({ filters: otherFilters }); return metrics.map((m) => ({ label: m.text, value: m.text })); }; const fetchFilterKeysWithRemove = async ( datasource: DataSourceRef, - getTagKeysOptions?: any + currentKey: string | null, + allFilters: AdHocVariableFilter[] ): Promise>> => { - const keys = await fetchFilterKeys(datasource, getTagKeysOptions); + const keys = await fetchFilterKeys(datasource, currentKey, allFilters); return [REMOVE_VALUE, ...keys]; }; diff --git a/public/app/features/variables/adhoc/picker/AdHocFilterRenderer.tsx b/public/app/features/variables/adhoc/picker/AdHocFilterRenderer.tsx index 79babf88ead..12886ce6530 100644 --- a/public/app/features/variables/adhoc/picker/AdHocFilterRenderer.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocFilterRenderer.tsx @@ -10,6 +10,7 @@ import { OperatorSegment } from './OperatorSegment'; interface Props { datasource: DataSourceRef; filter: AdHocVariableFilter; + allFilters: AdHocVariableFilter[]; onKeyChange: (item: SelectableValue) => void; onOperatorChange: (item: SelectableValue) => void; onValueChange: (item: SelectableValue) => void; @@ -25,7 +26,7 @@ export const AdHocFilterRenderer = ({ onOperatorChange, onValueChange, placeHolder, - getTagKeysOptions, + allFilters, disabled, }: Props) => { return ( @@ -35,7 +36,7 @@ export const AdHocFilterRenderer = ({ datasource={datasource} filterKey={key} onChange={onKeyChange} - getTagKeysOptions={getTagKeysOptions} + allFilters={allFilters} />
@@ -45,6 +46,7 @@ export const AdHocFilterRenderer = ({ datasource={datasource} filterKey={key} filterValue={value} + allFilters={allFilters} onChange={onValueChange} placeHolder={placeHolder} /> diff --git a/public/app/features/variables/adhoc/picker/AdHocFilterValue.tsx b/public/app/features/variables/adhoc/picker/AdHocFilterValue.tsx index 7e6d13ec139..a1cf784f2cb 100644 --- a/public/app/features/variables/adhoc/picker/AdHocFilterValue.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocFilterValue.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { DataSourceRef, MetricFindValue, SelectableValue } from '@grafana/data'; +import { AdHocVariableFilter, DataSourceRef, MetricFindValue, SelectableValue } from '@grafana/data'; import { SegmentAsync } from '@grafana/ui'; import { getDatasourceSrv } from '../../../plugins/datasource_srv'; @@ -12,10 +12,19 @@ interface Props { onChange: (item: SelectableValue) => void; placeHolder?: string; disabled?: boolean; + allFilters: AdHocVariableFilter[]; } -export const AdHocFilterValue = ({ datasource, disabled, onChange, filterKey, filterValue, placeHolder }: Props) => { - const loadValues = () => fetchFilterValues(datasource, filterKey); +export const AdHocFilterValue = ({ + datasource, + disabled, + onChange, + filterKey, + filterValue, + placeHolder, + allFilters, +}: Props) => { + const loadValues = () => fetchFilterValues(datasource, filterKey, allFilters); return (
@@ -31,13 +40,19 @@ export const AdHocFilterValue = ({ datasource, disabled, onChange, filterKey, fi ); }; -const fetchFilterValues = async (datasource: DataSourceRef, key: string): Promise>> => { +const fetchFilterValues = async ( + datasource: DataSourceRef, + key: string, + allFilters: AdHocVariableFilter[] +): Promise>> => { const ds = await getDatasourceSrv().get(datasource); if (!ds || !ds.getTagValues) { return []; } - const metrics = await ds.getTagValues({ key }); + // Filter out the current filter key from the list of all filters + const otherFilters = allFilters.filter((f) => f.key !== key); + const metrics = await ds.getTagValues({ key, filters: otherFilters }); return metrics.map((m: MetricFindValue) => ({ label: m.text, value: m.text })); }; diff --git a/public/app/features/variables/adhoc/picker/AdHocPicker.tsx b/public/app/features/variables/adhoc/picker/AdHocPicker.tsx index d107d26215a..9c70288c839 100644 --- a/public/app/features/variables/adhoc/picker/AdHocPicker.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocPicker.tsx @@ -42,12 +42,13 @@ export class AdHocPickerUnconnected extends PureComponent { }; render() { - const { filters, datasource } = this.props.variable; + const { filters, datasource, baseFilters } = this.props.variable; return ( ({ label: variable, value: variable })); if (!metric) { // get all the labels - datasource.getTagKeys().then((labelNames: Array<{ text: string }>) => { + datasource.getTagKeys({ filters: [] }).then((labelNames: Array<{ text: string }>) => { const names = labelNames.map(({ text }) => ({ label: text, value: text })); setLabelOptions([...variables, ...names]); }); diff --git a/public/app/plugins/datasource/prometheus/datasource.test.ts b/public/app/plugins/datasource/prometheus/datasource.test.ts index 125d0b25592..c3eb2c08062 100644 --- a/public/app/plugins/datasource/prometheus/datasource.test.ts +++ b/public/app/plugins/datasource/prometheus/datasource.test.ts @@ -172,10 +172,14 @@ describe('PrometheusDatasource', () => { ).rejects.toMatchObject({ message: expect.stringMatching('Browser access'), }); - await expect(directDs.getTagKeys()).rejects.toMatchObject({ - message: expect.stringMatching('Browser access'), - }); - await expect(directDs.getTagValues()).rejects.toMatchObject({ + + const errorMock = jest.spyOn(console, 'error').mockImplementation(() => {}); + + await directDs.getTagKeys({ filters: [] }); + // Language provider currently catches and just logs the error + expect(errorMock).toHaveBeenCalledTimes(1); + + await expect(directDs.getTagValues({ filters: [], key: 'A' })).rejects.toMatchObject({ message: expect.stringMatching('Browser access'), }); }); diff --git a/public/app/plugins/datasource/prometheus/datasource.tsx b/public/app/plugins/datasource/prometheus/datasource.tsx index c233cb1d6ca..365d78115cb 100644 --- a/public/app/plugins/datasource/prometheus/datasource.tsx +++ b/public/app/plugins/datasource/prometheus/datasource.tsx @@ -24,6 +24,9 @@ import { ScopedVars, TimeRange, renderLegendFormat, + DataSourceGetTagKeysOptions, + DataSourceGetTagValuesOptions, + MetricFindValue, } from '@grafana/data'; import { BackendDataSourceResponse, @@ -55,7 +58,8 @@ import { } from './language_utils'; import PrometheusMetricFindQuery from './metric_find_query'; import { getInitHints, getQueryHints } from './query_hints'; -import { QueryEditorMode } from './querybuilder/shared/types'; +import { promQueryModeller } from './querybuilder/PromQueryModeller'; +import { QueryBuilderLabelFilter, QueryEditorMode } from './querybuilder/shared/types'; import { CacheRequestInfo, defaultPrometheusQueryOverlapWindow, QueryCache } from './querycache/QueryCache'; import { getOriginalMetricName, transform, transformV2 } from './result_transformer'; import { trackQuery } from './tracking'; @@ -971,27 +975,50 @@ export class PrometheusDatasource // this is used to get label keys, a.k.a label names // it is used in metric_find_query.ts // and in Tempo here grafana/public/app/plugins/datasource/tempo/QueryEditor/ServiceGraphSection.tsx - async getTagKeys(options?: { series: string[] }) { - if (options?.series) { - // Get tags for the provided series only - const seriesLabels: Array> = await Promise.all( - options.series.map((series: string) => this.languageProvider.fetchSeriesLabels(series)) - ); - // Combines tags from all options.series provided - let tags: string[] = []; - seriesLabels.map((value) => (tags = tags.concat(Object.keys(value)))); - const uniqueLabels = [...new Set(tags)]; - return uniqueLabels.map((value: any) => ({ text: value })); - } else { - // Get all tags - const params = this.getTimeRangeParams(); - const result = await this.metadataRequest('/api/v1/labels', params); - return result?.data?.data?.map((value: any) => ({ text: value })) ?? []; + async getTagKeys(options: DataSourceGetTagKeysOptions): Promise { + if (!options || options.filters.length === 0) { + await this.languageProvider.fetchLabels(); + return this.languageProvider.getLabelKeys().map((k) => ({ value: k, text: k })); } + + const labelFilters: QueryBuilderLabelFilter[] = options.filters.map((f) => ({ + label: f.key, + value: f.value, + op: f.operator, + })); + const expr = promQueryModeller.renderLabels(labelFilters); + + let labelsIndex: Record; + + if (this.hasLabelsMatchAPISupport()) { + labelsIndex = await this.languageProvider.fetchSeriesLabelsMatch(expr); + } else { + labelsIndex = await this.languageProvider.fetchSeriesLabels(expr); + } + + // filter out already used labels + return Object.keys(labelsIndex) + .filter((labelName) => !options.filters.find((filter) => filter.key === labelName)) + .map((k) => ({ value: k, text: k })); } // By implementing getTagKeys and getTagValues we add ad-hoc filters functionality - async getTagValues(options: { key?: string } = {}) { + async getTagValues(options: DataSourceGetTagValuesOptions) { + const labelFilters: QueryBuilderLabelFilter[] = options.filters.map((f) => ({ + label: f.key, + value: f.value, + op: f.operator, + })); + + const expr = promQueryModeller.renderLabels(labelFilters); + + if (this.hasLabelsMatchAPISupport()) { + return (await this.languageProvider.fetchSeriesValuesWithMatch(options.key, expr)).map((v) => ({ + value: v, + text: v, + })); + } + const params = this.getTimeRangeParams(); const result = await this.metadataRequest(`/api/v1/label/${options.key}/values`, params); return result?.data?.data?.map((value: any) => ({ text: value })) ?? []; diff --git a/public/app/plugins/datasource/prometheus/metric_find_query.ts b/public/app/plugins/datasource/prometheus/metric_find_query.ts index e5a0af82e60..6903dfea91e 100644 --- a/public/app/plugins/datasource/prometheus/metric_find_query.ts +++ b/public/app/plugins/datasource/prometheus/metric_find_query.ts @@ -46,7 +46,7 @@ export default class PrometheusMetricFindQuery { } if (labelNamesQuery) { - return this.datasource.getTagKeys(); + return this.datasource.getTagKeys({ filters: [] }); } const labelValuesQuery = this.query.match(labelValuesRegex); diff --git a/public/app/plugins/datasource/tempo/ServiceGraphSection.tsx b/public/app/plugins/datasource/tempo/ServiceGraphSection.tsx index b8ff86a6874..53bba555f23 100644 --- a/public/app/plugins/datasource/tempo/ServiceGraphSection.tsx +++ b/public/app/plugins/datasource/tempo/ServiceGraphSection.tsx @@ -22,7 +22,6 @@ export function ServiceGraphSection({ onChange: (value: TempoQuery) => void; }) { const styles = useStyles2(getStyles); - const dsState = useAsync(() => getDS(graphDatasourceUid), [graphDatasourceUid]); // Check if service graph metrics are being collected. If not, displays a warning @@ -30,10 +29,14 @@ export function ServiceGraphSection({ useEffect(() => { async function fn(ds: PrometheusDatasource) { const keys = await ds.getTagKeys({ - series: [ - 'traces_service_graph_request_server_seconds_sum', - 'traces_service_graph_request_total', - 'traces_service_graph_request_failed_total', + filters: [ + { + key: '__name__', + operator: '=~', + value: + 'traces_service_graph_request_server_seconds_sum|traces_service_graph_request_total|traces_service_graph_request_failed_total', + condition: '', + }, ], }); setHasKeys(Boolean(keys.length)); @@ -61,6 +64,7 @@ export function ServiceGraphSection({
); } + const filters = queryToFilter(query.serviceMapQuery || ''); return ( @@ -70,9 +74,14 @@ export function ServiceGraphSection({ { onChange({ ...query,