mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Templating: Adds -- remove filter -- back to incomplete AdHoc filters (#26829)
This commit is contained in:
parent
9a694b2b5a
commit
0a40862af5
@ -1,77 +1,63 @@
|
|||||||
import React, { FC, ReactElement, useState } from 'react';
|
import React, { FC, useCallback, useState } from 'react';
|
||||||
import { Icon, SegmentAsync } from '@grafana/ui';
|
|
||||||
import { OperatorSegment } from './OperatorSegment';
|
|
||||||
import { AdHocVariableFilter } from 'app/features/variables/types';
|
import { AdHocVariableFilter } from 'app/features/variables/types';
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
import { AdHocFilterKey, REMOVE_FILTER_KEY } from './AdHocFilterKey';
|
||||||
|
import { AdHocFilterRenderer } from './AdHocFilterRenderer';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onLoadKeys: () => Promise<Array<SelectableValue<string>>>;
|
datasource: string;
|
||||||
onLoadValues: (key: string) => Promise<Array<SelectableValue<string>>>;
|
|
||||||
onCompleted: (filter: AdHocVariableFilter) => void;
|
onCompleted: (filter: AdHocVariableFilter) => void;
|
||||||
appendBefore?: React.ReactNode;
|
appendBefore?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AdHocFilterBuilder: FC<Props> = ({ appendBefore, onCompleted, onLoadKeys, onLoadValues }) => {
|
export const AdHocFilterBuilder: FC<Props> = ({ datasource, appendBefore, onCompleted }) => {
|
||||||
const [key, setKey] = useState<string | null>(null);
|
const [key, setKey] = useState<string | null>(null);
|
||||||
const [operator, setOperator] = useState<string>('=');
|
const [operator, setOperator] = useState<string>('=');
|
||||||
|
|
||||||
|
const onKeyChanged = useCallback(
|
||||||
|
(item: SelectableValue<string | null>) => {
|
||||||
|
if (item.value !== REMOVE_FILTER_KEY) {
|
||||||
|
setKey(item.value ?? '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setKey(null);
|
||||||
|
},
|
||||||
|
[setKey]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onOperatorChanged = useCallback((item: SelectableValue<string>) => setOperator(item.value ?? ''), [
|
||||||
|
setOperator,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const onValueChanged = useCallback(
|
||||||
|
(item: SelectableValue<string>) => {
|
||||||
|
onCompleted({
|
||||||
|
value: item.value ?? '',
|
||||||
|
operator: operator,
|
||||||
|
condition: '',
|
||||||
|
key: key!,
|
||||||
|
});
|
||||||
|
setKey(null);
|
||||||
|
setOperator('=');
|
||||||
|
},
|
||||||
|
[onCompleted, key, setKey, setOperator]
|
||||||
|
);
|
||||||
|
|
||||||
if (key === null) {
|
if (key === null) {
|
||||||
return (
|
return <AdHocFilterKey datasource={datasource} filterKey={key} onChange={onKeyChanged} />;
|
||||||
<div className="gf-form">
|
|
||||||
<SegmentAsync
|
|
||||||
className="query-segment-key"
|
|
||||||
Component={filterAddButton(key)}
|
|
||||||
value={key}
|
|
||||||
onChange={({ value }) => setKey(value ?? '')}
|
|
||||||
loadOptions={onLoadKeys}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment key="filter-builder">
|
<React.Fragment key="filter-builder">
|
||||||
{appendBefore}
|
{appendBefore}
|
||||||
<div className="gf-form">
|
<AdHocFilterRenderer
|
||||||
<SegmentAsync
|
datasource={datasource}
|
||||||
className="query-segment-key"
|
filter={{ key, value: '', operator, condition: '' }}
|
||||||
value={key}
|
placeHolder="select value"
|
||||||
onChange={({ value }) => setKey(value ?? '')}
|
onKeyChange={onKeyChanged}
|
||||||
loadOptions={onLoadKeys}
|
onOperatorChange={onOperatorChanged}
|
||||||
/>
|
onValueChange={onValueChanged}
|
||||||
</div>
|
/>
|
||||||
<div className="gf-form">
|
|
||||||
<OperatorSegment value={operator} onChange={({ value }) => setOperator(value ?? '')} />
|
|
||||||
</div>
|
|
||||||
<div className="gf-form">
|
|
||||||
<SegmentAsync
|
|
||||||
className="query-segment-value"
|
|
||||||
placeholder="select value"
|
|
||||||
onChange={({ value }) => {
|
|
||||||
onCompleted({
|
|
||||||
value: value ?? '',
|
|
||||||
operator: operator,
|
|
||||||
condition: '',
|
|
||||||
key: key,
|
|
||||||
});
|
|
||||||
setKey(null);
|
|
||||||
setOperator('=');
|
|
||||||
}}
|
|
||||||
loadOptions={() => onLoadValues(key)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function filterAddButton(key: string | null): ReactElement | undefined {
|
|
||||||
if (key !== null) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<a className="gf-form-label query-part">
|
|
||||||
<Icon name="plus" />
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
import React, { FC, ReactElement } from 'react';
|
||||||
|
import { Icon, SegmentAsync } from '@grafana/ui';
|
||||||
|
import { getDatasourceSrv } from '../../../plugins/datasource_srv';
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
datasource: string;
|
||||||
|
filterKey: string | null;
|
||||||
|
onChange: (item: SelectableValue<string | null>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AdHocFilterKey: FC<Props> = ({ datasource, onChange, filterKey }) => {
|
||||||
|
const loadKeys = () => fetchFilterKeys(datasource);
|
||||||
|
const loadKeysWithRemove = () => fetchFilterKeysWithRemove(datasource);
|
||||||
|
|
||||||
|
if (filterKey === null) {
|
||||||
|
return (
|
||||||
|
<div className="gf-form">
|
||||||
|
<SegmentAsync
|
||||||
|
className="query-segment-key"
|
||||||
|
Component={plusSegment}
|
||||||
|
value={filterKey}
|
||||||
|
onChange={onChange}
|
||||||
|
loadOptions={loadKeys}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="gf-form">
|
||||||
|
<SegmentAsync
|
||||||
|
className="query-segment-key"
|
||||||
|
value={filterKey}
|
||||||
|
onChange={onChange}
|
||||||
|
loadOptions={loadKeysWithRemove}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const REMOVE_FILTER_KEY = '-- remove filter --';
|
||||||
|
const REMOVE_VALUE = { label: REMOVE_FILTER_KEY, value: REMOVE_FILTER_KEY };
|
||||||
|
|
||||||
|
const plusSegment: ReactElement = (
|
||||||
|
<a className="gf-form-label query-part">
|
||||||
|
<Icon name="plus" />
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchFilterKeys = async (datasource: string): Promise<Array<SelectableValue<string>>> => {
|
||||||
|
const ds = await getDatasourceSrv().get(datasource);
|
||||||
|
|
||||||
|
if (!ds || !ds.getTagKeys) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const metrics = await ds.getTagKeys();
|
||||||
|
return metrics.map(m => ({ label: m.text, value: m.text }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchFilterKeysWithRemove = async (datasource: string): Promise<Array<SelectableValue<string>>> => {
|
||||||
|
const keys = await fetchFilterKeys(datasource);
|
||||||
|
return [REMOVE_VALUE, ...keys];
|
||||||
|
};
|
@ -0,0 +1,40 @@
|
|||||||
|
import React, { FC } from 'react';
|
||||||
|
import { OperatorSegment } from './OperatorSegment';
|
||||||
|
import { AdHocVariableFilter } from 'app/features/variables/types';
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
import { AdHocFilterKey } from './AdHocFilterKey';
|
||||||
|
import { AdHocFilterValue } from './AdHocFilterValue';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
datasource: string;
|
||||||
|
filter: AdHocVariableFilter;
|
||||||
|
onKeyChange: (item: SelectableValue<string | null>) => void;
|
||||||
|
onOperatorChange: (item: SelectableValue<string>) => void;
|
||||||
|
onValueChange: (item: SelectableValue<string>) => void;
|
||||||
|
placeHolder?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AdHocFilterRenderer: FC<Props> = ({
|
||||||
|
datasource,
|
||||||
|
filter: { key, operator, value },
|
||||||
|
onKeyChange,
|
||||||
|
onOperatorChange,
|
||||||
|
onValueChange,
|
||||||
|
placeHolder,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AdHocFilterKey datasource={datasource} filterKey={key} onChange={onKeyChange} />
|
||||||
|
<div className="gf-form">
|
||||||
|
<OperatorSegment value={operator} onChange={onOperatorChange} />
|
||||||
|
</div>
|
||||||
|
<AdHocFilterValue
|
||||||
|
datasource={datasource}
|
||||||
|
filterKey={key}
|
||||||
|
filterValue={value}
|
||||||
|
onChange={onValueChange}
|
||||||
|
placeHolder={placeHolder}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,39 @@
|
|||||||
|
import React, { FC } from 'react';
|
||||||
|
import { SegmentAsync } from '@grafana/ui';
|
||||||
|
import { getDatasourceSrv } from '../../../plugins/datasource_srv';
|
||||||
|
import { MetricFindValue, SelectableValue } from '@grafana/data';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
datasource: string;
|
||||||
|
filterKey: string;
|
||||||
|
filterValue: string | null;
|
||||||
|
onChange: (item: SelectableValue<string>) => void;
|
||||||
|
placeHolder?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AdHocFilterValue: FC<Props> = ({ datasource, onChange, filterKey, filterValue, placeHolder }) => {
|
||||||
|
const loadValues = () => fetchFilterValues(datasource, filterKey);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="gf-form">
|
||||||
|
<SegmentAsync
|
||||||
|
className="query-segment-value"
|
||||||
|
placeholder={placeHolder}
|
||||||
|
value={filterValue}
|
||||||
|
onChange={onChange}
|
||||||
|
loadOptions={loadValues}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchFilterValues = async (datasource: string, key: string): Promise<Array<SelectableValue<string>>> => {
|
||||||
|
const ds = await getDatasourceSrv().get(datasource);
|
||||||
|
|
||||||
|
if (!ds || !ds.getTagValues) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const metrics = await ds.getTagValues({ key });
|
||||||
|
return metrics.map((m: MetricFindValue) => ({ label: m.text, value: m.text }));
|
||||||
|
};
|
@ -2,14 +2,13 @@ import React, { PureComponent, ReactNode } from 'react';
|
|||||||
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
|
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
|
||||||
import { StoreState } from 'app/types';
|
import { StoreState } from 'app/types';
|
||||||
import { AdHocVariableFilter, AdHocVariableModel } from 'app/features/variables/types';
|
import { AdHocVariableFilter, AdHocVariableModel } from 'app/features/variables/types';
|
||||||
import { SegmentAsync } from '@grafana/ui';
|
|
||||||
import { VariablePickerProps } from '../../pickers/types';
|
import { VariablePickerProps } from '../../pickers/types';
|
||||||
import { OperatorSegment } from './OperatorSegment';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { MetricFindValue, SelectableValue } from '@grafana/data';
|
|
||||||
import { AdHocFilterBuilder } from './AdHocFilterBuilder';
|
import { AdHocFilterBuilder } from './AdHocFilterBuilder';
|
||||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
|
||||||
import { ConditionSegment } from './ConditionSegment';
|
import { ConditionSegment } from './ConditionSegment';
|
||||||
import { addFilter, changeFilter, removeFilter } from '../actions';
|
import { addFilter, changeFilter, removeFilter } from '../actions';
|
||||||
|
import { REMOVE_FILTER_KEY } from './AdHocFilterKey';
|
||||||
|
import { AdHocFilterRenderer } from './AdHocFilterRenderer';
|
||||||
|
|
||||||
interface OwnProps extends VariablePickerProps<AdHocVariableModel> {}
|
interface OwnProps extends VariablePickerProps<AdHocVariableModel> {}
|
||||||
|
|
||||||
@ -23,8 +22,6 @@ interface DispatchProps {
|
|||||||
|
|
||||||
type Props = OwnProps & ConnectedProps & DispatchProps;
|
type Props = OwnProps & ConnectedProps & DispatchProps;
|
||||||
|
|
||||||
const REMOVE_FILTER_KEY = '-- remove filter --';
|
|
||||||
const REMOVE_VALUE = { label: REMOVE_FILTER_KEY, value: REMOVE_FILTER_KEY };
|
|
||||||
export class AdHocPickerUnconnected extends PureComponent<Props> {
|
export class AdHocPickerUnconnected extends PureComponent<Props> {
|
||||||
onChange = (index: number, prop: string) => (key: SelectableValue<string>) => {
|
onChange = (index: number, prop: string) => (key: SelectableValue<string>) => {
|
||||||
const { id, filters } = this.props.variable;
|
const { id, filters } = this.props.variable;
|
||||||
@ -48,35 +45,6 @@ export class AdHocPickerUnconnected extends PureComponent<Props> {
|
|||||||
this.props.addFilter(id, filter);
|
this.props.addFilter(id, filter);
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchFilterKeys = async () => {
|
|
||||||
const { variable } = this.props;
|
|
||||||
const ds = await getDatasourceSrv().get(variable.datasource!);
|
|
||||||
|
|
||||||
if (!ds || !ds.getTagKeys) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const metrics = await ds.getTagKeys();
|
|
||||||
return metrics.map(m => ({ label: m.text, value: m.text }));
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchFilterKeysWithRemove = async () => {
|
|
||||||
const keys = await this.fetchFilterKeys();
|
|
||||||
return [REMOVE_VALUE, ...keys];
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchFilterValues = async (key: string) => {
|
|
||||||
const { variable } = this.props;
|
|
||||||
const ds = await getDatasourceSrv().get(variable.datasource!);
|
|
||||||
|
|
||||||
if (!ds || !ds.getTagValues) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const metrics = await ds.getTagValues({ key });
|
|
||||||
return metrics.map((m: MetricFindValue) => ({ label: m.text, value: m.text }));
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { filters } = this.props.variable;
|
const { filters } = this.props.variable;
|
||||||
|
|
||||||
@ -84,9 +52,8 @@ export class AdHocPickerUnconnected extends PureComponent<Props> {
|
|||||||
<div className="gf-form-inline">
|
<div className="gf-form-inline">
|
||||||
{this.renderFilters(filters)}
|
{this.renderFilters(filters)}
|
||||||
<AdHocFilterBuilder
|
<AdHocFilterBuilder
|
||||||
|
datasource={this.props.variable.datasource!}
|
||||||
appendBefore={filters.length > 0 ? <ConditionSegment label="AND" /> : null}
|
appendBefore={filters.length > 0 ? <ConditionSegment label="AND" /> : null}
|
||||||
onLoadKeys={this.fetchFilterKeys}
|
|
||||||
onLoadValues={this.fetchFilterValues}
|
|
||||||
onCompleted={this.appendFilterToVariable}
|
onCompleted={this.appendFilterToVariable}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -96,7 +63,7 @@ export class AdHocPickerUnconnected extends PureComponent<Props> {
|
|||||||
renderFilters(filters: AdHocVariableFilter[]) {
|
renderFilters(filters: AdHocVariableFilter[]) {
|
||||||
return filters.reduce((segments: ReactNode[], filter, index) => {
|
return filters.reduce((segments: ReactNode[], filter, index) => {
|
||||||
if (segments.length > 0) {
|
if (segments.length > 0) {
|
||||||
segments.push(<ConditionSegment label="AND" />);
|
segments.push(<ConditionSegment label="AND" key={`condition-${index}`} />);
|
||||||
}
|
}
|
||||||
segments.push(this.renderFilterSegments(filter, index));
|
segments.push(this.renderFilterSegments(filter, index));
|
||||||
return segments;
|
return segments;
|
||||||
@ -106,25 +73,13 @@ export class AdHocPickerUnconnected extends PureComponent<Props> {
|
|||||||
renderFilterSegments(filter: AdHocVariableFilter, index: number) {
|
renderFilterSegments(filter: AdHocVariableFilter, index: number) {
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={`filter-${index}`}>
|
<React.Fragment key={`filter-${index}`}>
|
||||||
<div className="gf-form">
|
<AdHocFilterRenderer
|
||||||
<SegmentAsync
|
datasource={this.props.variable.datasource!}
|
||||||
className="query-segment-key"
|
filter={filter}
|
||||||
value={filter.key}
|
onKeyChange={this.onChange(index, 'key')}
|
||||||
onChange={this.onChange(index, 'key')}
|
onOperatorChange={this.onChange(index, 'operator')}
|
||||||
loadOptions={this.fetchFilterKeysWithRemove}
|
onValueChange={this.onChange(index, 'value')}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="gf-form">
|
|
||||||
<OperatorSegment value={filter.operator} onChange={this.onChange(index, 'operator')} />
|
|
||||||
</div>
|
|
||||||
<div className="gf-form">
|
|
||||||
<SegmentAsync
|
|
||||||
className="query-segment-value"
|
|
||||||
value={filter.value}
|
|
||||||
onChange={this.onChange(index, 'value')}
|
|
||||||
loadOptions={() => this.fetchFilterValues(filter.key)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user