Tempo: Filter available service graph filter values to most relevant options (#43728)

* Restrict service graph filter tag values to labels from service graph metrics
This commit is contained in:
Connor Lindsey 2022-01-06 07:09:31 -07:00 committed by GitHub
parent cb34c47123
commit 1a7567d8c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 58 additions and 13 deletions

View File

@ -12,6 +12,9 @@ interface Props {
addFilter: (filter: AdHocVariableFilter) => void; addFilter: (filter: AdHocVariableFilter) => void;
removeFilter: (index: number) => void; removeFilter: (index: number) => void;
changeFilter: (index: number, newFilter: AdHocVariableFilter) => 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;
} }
/** /**
@ -51,6 +54,7 @@ export class AdHocFilter extends PureComponent<Props> {
datasource={this.props.datasource!} datasource={this.props.datasource!}
appendBefore={filters.length > 0 ? <ConditionSegment label="AND" /> : null} appendBefore={filters.length > 0 ? <ConditionSegment label="AND" /> : null}
onCompleted={this.appendFilterToVariable} onCompleted={this.appendFilterToVariable}
getTagKeysOptions={this.props.getTagKeysOptions}
/> />
</div> </div>
); );
@ -75,6 +79,7 @@ export class AdHocFilter extends PureComponent<Props> {
onKeyChange={this.onChange(index, 'key')} onKeyChange={this.onChange(index, 'key')}
onOperatorChange={this.onChange(index, 'operator')} onOperatorChange={this.onChange(index, 'operator')}
onValueChange={this.onChange(index, 'value')} onValueChange={this.onChange(index, 'value')}
getTagKeysOptions={this.props.getTagKeysOptions}
/> />
</React.Fragment> </React.Fragment>
); );

View File

@ -8,9 +8,10 @@ interface Props {
datasource: DataSourceRef; datasource: DataSourceRef;
onCompleted: (filter: AdHocVariableFilter) => void; onCompleted: (filter: AdHocVariableFilter) => void;
appendBefore?: React.ReactNode; appendBefore?: React.ReactNode;
getTagKeysOptions?: any;
} }
export const AdHocFilterBuilder: FC<Props> = ({ datasource, appendBefore, onCompleted }) => { export const AdHocFilterBuilder: FC<Props> = ({ datasource, appendBefore, onCompleted, getTagKeysOptions }) => {
const [key, setKey] = useState<string | null>(null); const [key, setKey] = useState<string | null>(null);
const [operator, setOperator] = useState<string>('='); const [operator, setOperator] = useState<string>('=');
@ -44,7 +45,14 @@ export const AdHocFilterBuilder: FC<Props> = ({ datasource, appendBefore, onComp
); );
if (key === null) { if (key === null) {
return <AdHocFilterKey datasource={datasource} filterKey={key} onChange={onKeyChanged} />; return (
<AdHocFilterKey
datasource={datasource}
filterKey={key}
onChange={onKeyChanged}
getTagKeysOptions={getTagKeysOptions}
/>
);
} }
return ( return (
@ -57,6 +65,7 @@ export const AdHocFilterBuilder: FC<Props> = ({ datasource, appendBefore, onComp
onKeyChange={onKeyChanged} onKeyChange={onKeyChanged}
onOperatorChange={onOperatorChanged} onOperatorChange={onOperatorChanged}
onValueChange={onValueChanged} onValueChange={onValueChanged}
getTagKeysOptions={getTagKeysOptions}
/> />
</React.Fragment> </React.Fragment>
); );

View File

@ -7,12 +7,13 @@ interface Props {
datasource: DataSourceRef; datasource: DataSourceRef;
filterKey: string | null; filterKey: string | null;
onChange: (item: SelectableValue<string | null>) => void; onChange: (item: SelectableValue<string | null>) => void;
getTagKeysOptions?: any;
} }
const MIN_WIDTH = 90; const MIN_WIDTH = 90;
export const AdHocFilterKey: FC<Props> = ({ datasource, onChange, filterKey }) => { export const AdHocFilterKey: FC<Props> = ({ datasource, onChange, filterKey, getTagKeysOptions }) => {
const loadKeys = () => fetchFilterKeys(datasource); const loadKeys = () => fetchFilterKeys(datasource, getTagKeysOptions);
const loadKeysWithRemove = () => fetchFilterKeysWithRemove(datasource); const loadKeysWithRemove = () => fetchFilterKeysWithRemove(datasource, getTagKeysOptions);
if (filterKey === null) { if (filterKey === null) {
return ( return (
@ -51,18 +52,24 @@ const plusSegment: ReactElement = (
</a> </a>
); );
const fetchFilterKeys = async (datasource: DataSourceRef): Promise<Array<SelectableValue<string>>> => { const fetchFilterKeys = async (
datasource: DataSourceRef,
getTagKeysOptions?: any
): Promise<Array<SelectableValue<string>>> => {
const ds = await getDatasourceSrv().get(datasource); const ds = await getDatasourceSrv().get(datasource);
if (!ds || !ds.getTagKeys) { if (!ds || !ds.getTagKeys) {
return []; return [];
} }
const metrics = await ds.getTagKeys(); const metrics = await ds.getTagKeys(getTagKeysOptions);
return metrics.map((m) => ({ label: m.text, value: m.text })); return metrics.map((m) => ({ label: m.text, value: m.text }));
}; };
const fetchFilterKeysWithRemove = async (datasource: DataSourceRef): Promise<Array<SelectableValue<string>>> => { const fetchFilterKeysWithRemove = async (
const keys = await fetchFilterKeys(datasource); datasource: DataSourceRef,
getTagKeysOptions?: any
): Promise<Array<SelectableValue<string>>> => {
const keys = await fetchFilterKeys(datasource, getTagKeysOptions);
return [REMOVE_VALUE, ...keys]; return [REMOVE_VALUE, ...keys];
}; };

View File

@ -12,6 +12,7 @@ interface Props {
onOperatorChange: (item: SelectableValue<string>) => void; onOperatorChange: (item: SelectableValue<string>) => void;
onValueChange: (item: SelectableValue<string>) => void; onValueChange: (item: SelectableValue<string>) => void;
placeHolder?: string; placeHolder?: string;
getTagKeysOptions?: any;
} }
export const AdHocFilterRenderer: FC<Props> = ({ export const AdHocFilterRenderer: FC<Props> = ({
@ -21,10 +22,16 @@ export const AdHocFilterRenderer: FC<Props> = ({
onOperatorChange, onOperatorChange,
onValueChange, onValueChange,
placeHolder, placeHolder,
getTagKeysOptions,
}) => { }) => {
return ( return (
<> <>
<AdHocFilterKey datasource={datasource} filterKey={key} onChange={onKeyChange} /> <AdHocFilterKey
datasource={datasource}
filterKey={key}
onChange={onKeyChange}
getTagKeysOptions={getTagKeysOptions}
/>
<div className="gf-form"> <div className="gf-form">
<OperatorSegment value={operator} onChange={onOperatorChange} /> <OperatorSegment value={operator} onChange={onOperatorChange} />
</div> </div>

View File

@ -790,9 +790,19 @@ export class PrometheusDatasource
); );
} }
async getTagKeys() { async getTagKeys(options?: any) {
const result = await this.metadataRequest('/api/v1/labels'); if (options?.series) {
return result?.data?.data?.map((value: any) => ({ text: value })) ?? []; // Get tags for the provided series only
const seriesLabels: Array<Record<string, string[]>> = await Promise.all(
options.series.map((series: string) => this.languageProvider.fetchSeriesLabels(series))
);
const uniqueLabels = [...new Set(...seriesLabels.map((value) => Object.keys(value)))];
return uniqueLabels.map((value: any) => ({ text: value }));
} else {
// Get all tags
const result = await this.metadataRequest('/api/v1/labels');
return result?.data?.data?.map((value: any) => ({ text: value })) ?? [];
}
} }
async getTagValues(options: { key?: string } = {}) { async getTagValues(options: { key?: string } = {}) {

View File

@ -44,6 +44,13 @@ export function ServiceGraphSection({
<AdHocFilter <AdHocFilter
datasource={{ uid: graphDatasourceUid }} datasource={{ uid: graphDatasourceUid }}
filters={filters} filters={filters}
getTagKeysOptions={{
series: [
'traces_service_graph_request_server_seconds_sum',
'traces_service_graph_request_total',
'traces_service_graph_request_failed_total',
],
}}
addFilter={(filter: AdHocVariableFilter) => { addFilter={(filter: AdHocVariableFilter) => {
onChange({ onChange({
...query, ...query,