Explore metrics: Add limit for adhoc filters options in providers functions (#94036)

* add limit for adhoc filters in providers functions

* add comments to describe function

* return early if filtersVariable is not an instance of AdHocFiltersVariable

* update function comments

* add tests to confirm the providers are limited to 10000
pull/94329/head
Brendan O'Handley 8 months ago committed by GitHub
parent f9361bf5bf
commit aefe08f738
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 14
      public/app/features/trails/DataTrail.tsx
  2. 56
      public/app/features/trails/utils.test.ts
  3. 71
      public/app/features/trails/utils.ts

@ -58,7 +58,7 @@ import {
VAR_OTEL_JOIN_QUERY,
VAR_OTEL_RESOURCES,
} from './shared';
import { getTrailFor } from './utils';
import { getTrailFor, limitAdhocProviders } from './utils';
export interface DataTrailState extends SceneObjectState {
topScene?: SceneObject;
@ -574,15 +574,15 @@ export class DataTrail extends SceneObjectBase<DataTrailState> {
const otelResourcesVariable = sceneGraph.lookupVariable(VAR_OTEL_RESOURCES, model);
const otelDepEnvVariable = sceneGraph.lookupVariable(VAR_OTEL_DEPLOYMENT_ENV, model);
const otelJoinQueryVariable = sceneGraph.lookupVariable(VAR_OTEL_JOIN_QUERY, model);
const filtersvariable = sceneGraph.lookupVariable(VAR_FILTERS, model);
const filtersVariable = sceneGraph.lookupVariable(VAR_FILTERS, model);
if (
otelResourcesVariable instanceof AdHocFiltersVariable &&
otelDepEnvVariable instanceof CustomVariable &&
otelJoinQueryVariable instanceof ConstantVariable &&
filtersvariable instanceof AdHocFiltersVariable
filtersVariable instanceof AdHocFiltersVariable
) {
model.resetOtelExperience(otelResourcesVariable, otelDepEnvVariable, otelJoinQueryVariable, filtersvariable);
model.resetOtelExperience(otelResourcesVariable, otelDepEnvVariable, otelJoinQueryVariable, filtersVariable);
}
} else {
// if experience is enabled, check standardization and update the otel variables
@ -590,6 +590,12 @@ export class DataTrail extends SceneObjectBase<DataTrailState> {
}
}, [model, hasOtelResources, useOtelExperience]);
useEffect(() => {
const filtersVariable = sceneGraph.lookupVariable(VAR_FILTERS, model);
const datasourceHelper = model.datasourceHelper;
limitAdhocProviders(filtersVariable, datasourceHelper);
}, [model]);
return (
<div className={styles.container}>
{showHeaderForFirstTimeUsers && <MetricsHeader />}

@ -0,0 +1,56 @@
import { AdHocFiltersVariable } from '@grafana/scenes';
import { MetricDatasourceHelper } from './helpers/MetricDatasourceHelper';
import { limitAdhocProviders } from './utils';
describe('limitAdhocProviders', () => {
let filtersVariable: AdHocFiltersVariable;
let datasourceHelper: MetricDatasourceHelper;
beforeEach(() => {
// disable console.log called in Scenes for this test
// called in scenes/packages/scenes/src/variables/adhoc/patchGetAdhocFilters.ts
jest.spyOn(console, 'log').mockImplementation(jest.fn());
filtersVariable = new AdHocFiltersVariable({
name: 'testVariable',
label: 'Test Variable',
type: 'adhoc',
});
datasourceHelper = {
getTagKeys: jest.fn().mockResolvedValue(Array(20000).fill({ text: 'key' })),
getTagValues: jest.fn().mockResolvedValue(Array(20000).fill({ text: 'value' })),
} as unknown as MetricDatasourceHelper;
});
afterAll(() => {
jest.restoreAllMocks();
});
it('should limit the number of tag keys returned in the variable to 10000', async () => {
limitAdhocProviders(filtersVariable, datasourceHelper);
if (filtersVariable instanceof AdHocFiltersVariable && filtersVariable.state.getTagKeysProvider) {
console.log = jest.fn();
const result = await filtersVariable.state.getTagKeysProvider(filtersVariable, null);
expect(result.values).toHaveLength(10000);
expect(result.replace).toBe(true);
}
});
it('should limit the number of tag values returned in the variable to 10000', async () => {
limitAdhocProviders(filtersVariable, datasourceHelper);
if (filtersVariable instanceof AdHocFiltersVariable && filtersVariable.state.getTagValuesProvider) {
const result = await filtersVariable.state.getTagValuesProvider(filtersVariable, {
key: 'testKey',
operator: '=',
value: 'testValue',
});
expect(result.values).toHaveLength(10000);
expect(result.replace).toBe(true);
}
});
});

@ -1,4 +1,4 @@
import { urlUtil } from '@grafana/data';
import { AdHocVariableFilter, GetTagResponse, MetricFindValue, urlUtil } from '@grafana/data';
import { config, getDataSourceSrv } from '@grafana/runtime';
import {
AdHocFiltersVariable,
@ -8,6 +8,8 @@ import {
SceneObjectUrlValues,
SceneTimeRange,
sceneUtils,
SceneVariable,
SceneVariableState,
} from '@grafana/scenes';
import { getDatasourceSrv } from '../plugins/datasource_srv';
@ -16,6 +18,7 @@ import { DataTrail } from './DataTrail';
import { DataTrailSettings } from './DataTrailSettings';
import { MetricScene } from './MetricScene';
import { getTrailStore } from './TrailStore/TrailStore';
import { MetricDatasourceHelper } from './helpers/MetricDatasourceHelper';
import { LOGS_METRIC, TRAILS_ROUTE, VAR_DATASOURCE_EXPR } from './shared';
export function getTrailFor(model: SceneObject): DataTrail {
@ -115,3 +118,69 @@ export function getFilters(scene: SceneObject) {
}
return null;
}
// frontend hardening limit
const MAX_ADHOC_VARIABLE_OPTIONS = 10000;
/**
* Add custom providers for the adhoc filters variable that limit the responses for labels keys and label values.
* Currently hard coded to 10000.
*
* The current provider functions for adhoc filter variables are the functions getTagKeys and getTagValues in the data source.
* This function still uses these functions from inside the data source helper.
*
* @param filtersVariable
* @param datasourceHelper
*/
export function limitAdhocProviders(
filtersVariable: SceneVariable<SceneVariableState> | null,
datasourceHelper: MetricDatasourceHelper
) {
if (!(filtersVariable instanceof AdHocFiltersVariable)) {
return;
}
filtersVariable.setState({
getTagKeysProvider: async (
variable: AdHocFiltersVariable,
currentKey: string | null
): Promise<{
replace?: boolean;
values: GetTagResponse | MetricFindValue[];
}> => {
// For the Prometheus label names endpoint, '/api/v1/labels'
// get the previously selected filters from the variable
// to use in the query to filter the response
// using filters, e.g. {previously_selected_label:"value"},
// as the series match[] parameter in Prometheus labels endpoint
const filters = filtersVariable.state.filters;
// call getTagKeys and truncate the response
const values = (await datasourceHelper.getTagKeys({ filters })).slice(0, MAX_ADHOC_VARIABLE_OPTIONS);
// use replace: true to override the default lookup in adhoc filter variable
return { replace: true, values };
},
getTagValuesProvider: async (
variable: AdHocFiltersVariable,
filter: AdHocVariableFilter
): Promise<{
replace?: boolean;
values: GetTagResponse | MetricFindValue[];
}> => {
// For the Prometheus label values endpoint, /api/v1/label/${interpolatedName}/values
// get the previously selected filters from the variable
// to use in the query to filter the response
// using filters, e.g. {previously_selected_label:"value"},
// as the series match[] parameter in Prometheus label values endpoint
const filtersValues = filtersVariable.state.filters;
// remove current selected filter if updating a chosen filter
const filters = filtersValues.filter((f) => f.key !== filter.key);
// call getTagValues and truncate the response
const values = (await datasourceHelper.getTagValues({ key: filter.key, filters })).slice(
0,
MAX_ADHOC_VARIABLE_OPTIONS
);
// use replace: true to override the default lookup in adhoc filter variable
return { replace: true, values };
},
});
}

Loading…
Cancel
Save