mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
committed by
GitHub
parent
f9361bf5bf
commit
aefe08f738
@@ -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 />}
|
||||
|
||||
56
public/app/features/trails/utils.test.ts
Normal file
56
public/app/features/trails/utils.test.ts
Normal file
@@ -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 };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user