Templating: Adds -- remove filter -- back to incomplete AdHoc filters (#26829)

This commit is contained in:
Hugo Häggmark 2020-08-10 04:40:32 -07:00 committed by GitHub
parent 9a694b2b5a
commit 0a40862af5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 199 additions and 114 deletions

View File

@ -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>
);
}

View File

@ -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];
};

View File

@ -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}
/>
</>
);
};

View File

@ -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 }));
};

View File

@ -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>
); );
} }