mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 08:35:43 -06:00
Explore: use GrafanaTheme2 (AdHocFilter component) (#37434)
* Explore: use GrafanaTheme2 and useStyles2 instead of the old ones * Explore: delete files and components that were'nt being used
This commit is contained in:
parent
e3fe4a2d11
commit
0b376522ac
@ -1,89 +0,0 @@
|
||||
import React from 'react';
|
||||
import { LegacyForms, useStyles } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { GrafanaTheme, SelectableValue } from '@grafana/data';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme) => ({
|
||||
keyValueContainer: css`
|
||||
label: key-value-container;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
`,
|
||||
});
|
||||
|
||||
enum ChangeType {
|
||||
Key = 'key',
|
||||
Value = 'value',
|
||||
Operator = 'operator',
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
keys: string[];
|
||||
keysPlaceHolder?: string;
|
||||
initialKey?: string;
|
||||
initialOperator?: string;
|
||||
initialValue?: string;
|
||||
values?: string[];
|
||||
valuesPlaceHolder?: string;
|
||||
onKeyChanged: (key: string) => void;
|
||||
onValueChanged: (value: string) => void;
|
||||
onOperatorChanged: (operator: string) => void;
|
||||
}
|
||||
|
||||
export const AdHocFilter: React.FunctionComponent<Props> = (props) => {
|
||||
const styles = useStyles(getStyles);
|
||||
|
||||
const onChange = (changeType: ChangeType) => (item: SelectableValue<string>) => {
|
||||
const { onKeyChanged, onValueChanged, onOperatorChanged } = props;
|
||||
|
||||
if (!item.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (changeType) {
|
||||
case ChangeType.Key:
|
||||
onKeyChanged(item.value);
|
||||
break;
|
||||
case ChangeType.Operator:
|
||||
onOperatorChanged(item.value);
|
||||
break;
|
||||
case ChangeType.Value:
|
||||
onValueChanged(item.value);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const stringToOption = (value: string) => ({ label: value, value: value });
|
||||
|
||||
const { keys, initialKey, keysPlaceHolder, initialOperator, values, initialValue, valuesPlaceHolder } = props;
|
||||
const operators = ['=', '!='];
|
||||
const keysAsOptions = keys ? keys.map(stringToOption) : [];
|
||||
const selectedKey = initialKey ? keysAsOptions.filter((option) => option.value === initialKey) : undefined;
|
||||
const valuesAsOptions = values ? values.map(stringToOption) : [];
|
||||
const selectedValue = initialValue ? valuesAsOptions.filter((option) => option.value === initialValue) : undefined;
|
||||
const operatorsAsOptions = operators.map(stringToOption);
|
||||
const selectedOperator = initialOperator
|
||||
? operatorsAsOptions.filter((option) => option.value === initialOperator)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div className={cx([styles.keyValueContainer])}>
|
||||
<Select
|
||||
options={keysAsOptions}
|
||||
isSearchable
|
||||
value={selectedKey}
|
||||
onChange={onChange(ChangeType.Key)}
|
||||
placeholder={keysPlaceHolder}
|
||||
/>
|
||||
<Select options={operatorsAsOptions} value={selectedOperator} onChange={onChange(ChangeType.Operator)} />
|
||||
<Select
|
||||
options={valuesAsOptions}
|
||||
isSearchable
|
||||
value={selectedValue}
|
||||
onChange={onChange(ChangeType.Value)}
|
||||
placeholder={valuesPlaceHolder}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,231 +0,0 @@
|
||||
import React from 'react';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import { DataSourceApi } from '@grafana/data';
|
||||
|
||||
import { AdHocFilterField, DEFAULT_REMOVE_FILTER_VALUE, KeyValuePair, Props } from './AdHocFilterField';
|
||||
import { AdHocFilter } from './AdHocFilter';
|
||||
import { MockDataSourceApi } from '../../../test/mocks/datasource_srv';
|
||||
|
||||
describe('<AdHocFilterField />', () => {
|
||||
let mockDataSourceApi: DataSourceApi;
|
||||
|
||||
beforeEach(() => {
|
||||
mockDataSourceApi = new MockDataSourceApi();
|
||||
});
|
||||
|
||||
it('should initially have no filters', () => {
|
||||
const mockOnPairsChanged = jest.fn();
|
||||
const wrapper = shallow(<AdHocFilterField datasource={mockDataSourceApi} onPairsChanged={mockOnPairsChanged} />);
|
||||
expect(wrapper.state('pairs')).toEqual([]);
|
||||
expect(wrapper.find(AdHocFilter).exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should add <AdHocFilter /> when onAddFilter is invoked', async () => {
|
||||
const mockOnPairsChanged = jest.fn();
|
||||
const wrapper = shallow(<AdHocFilterField datasource={mockDataSourceApi} onPairsChanged={mockOnPairsChanged} />);
|
||||
expect(wrapper.state('pairs')).toEqual([]);
|
||||
wrapper.find('button').first().simulate('click');
|
||||
const asyncCheck = setImmediate(() => {
|
||||
expect(wrapper.find(AdHocFilter).exists()).toBeTruthy();
|
||||
});
|
||||
global.clearImmediate(asyncCheck);
|
||||
});
|
||||
|
||||
it(`should remove the relevant filter when the '${DEFAULT_REMOVE_FILTER_VALUE}' key is selected`, () => {
|
||||
const mockOnPairsChanged = jest.fn();
|
||||
const wrapper = shallow(<AdHocFilterField datasource={mockDataSourceApi} onPairsChanged={mockOnPairsChanged} />);
|
||||
expect(wrapper.state('pairs')).toEqual([]);
|
||||
|
||||
wrapper.find('button').first().simulate('click');
|
||||
const asyncCheck = setImmediate(() => {
|
||||
expect(wrapper.find(AdHocFilter).exists()).toBeTruthy();
|
||||
|
||||
wrapper.find(AdHocFilter).prop('onKeyChanged')(DEFAULT_REMOVE_FILTER_VALUE);
|
||||
expect(wrapper.find(AdHocFilter).exists()).toBeFalsy();
|
||||
});
|
||||
global.clearImmediate(asyncCheck);
|
||||
});
|
||||
|
||||
it('it should call onPairsChanged when a filter is removed', async () => {
|
||||
const mockOnPairsChanged = jest.fn();
|
||||
const wrapper = shallow(<AdHocFilterField datasource={mockDataSourceApi} onPairsChanged={mockOnPairsChanged} />);
|
||||
expect(wrapper.state('pairs')).toEqual([]);
|
||||
|
||||
wrapper.find('button').first().simulate('click');
|
||||
const asyncCheck = setImmediate(() => {
|
||||
expect(wrapper.find(AdHocFilter).exists()).toBeTruthy();
|
||||
|
||||
wrapper.find(AdHocFilter).prop('onKeyChanged')(DEFAULT_REMOVE_FILTER_VALUE);
|
||||
expect(wrapper.find(AdHocFilter).exists()).toBeFalsy();
|
||||
|
||||
expect(mockOnPairsChanged.mock.calls.length).toBe(1);
|
||||
});
|
||||
global.clearImmediate(asyncCheck);
|
||||
});
|
||||
});
|
||||
|
||||
const setup = (propOverrides?: Partial<Props>) => {
|
||||
const datasource: DataSourceApi<any, any> = ({
|
||||
getTagKeys: jest.fn().mockReturnValue([{ text: 'key 1' }, { text: 'key 2' }]),
|
||||
getTagValues: jest.fn().mockReturnValue([{ text: 'value 1' }, { text: 'value 2' }]),
|
||||
} as unknown) as DataSourceApi<any, any>;
|
||||
|
||||
const props: Props = {
|
||||
datasource,
|
||||
onPairsChanged: jest.fn(),
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
|
||||
const wrapper = mount(<AdHocFilterField {...props} />);
|
||||
const instance = wrapper.instance() as AdHocFilterField;
|
||||
|
||||
return {
|
||||
instance,
|
||||
wrapper,
|
||||
datasource,
|
||||
};
|
||||
};
|
||||
|
||||
describe('AdHocFilterField', () => {
|
||||
describe('loadTagKeys', () => {
|
||||
describe('when called and there is no extendedOptions', () => {
|
||||
const { instance, datasource } = setup({ extendedOptions: undefined });
|
||||
|
||||
it('then it should return correct keys', async () => {
|
||||
const keys = await instance.loadTagKeys();
|
||||
|
||||
expect(keys).toEqual(['key 1', 'key 2']);
|
||||
});
|
||||
|
||||
it('then datasource.getTagKeys should be called with an empty object', async () => {
|
||||
await instance.loadTagKeys();
|
||||
|
||||
expect(datasource.getTagKeys).toBeCalledWith({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when called and there is extendedOptions', () => {
|
||||
const extendedOptions = { measurement: 'default' };
|
||||
const { instance, datasource } = setup({ extendedOptions });
|
||||
|
||||
it('then it should return correct keys', async () => {
|
||||
const keys = await instance.loadTagKeys();
|
||||
|
||||
expect(keys).toEqual(['key 1', 'key 2']);
|
||||
});
|
||||
|
||||
it('then datasource.getTagKeys should be called with extendedOptions', async () => {
|
||||
await instance.loadTagKeys();
|
||||
|
||||
expect(datasource.getTagKeys).toBeCalledWith(extendedOptions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadTagValues', () => {
|
||||
describe('when called and there is no extendedOptions', () => {
|
||||
const { instance, datasource } = setup({ extendedOptions: undefined });
|
||||
|
||||
it('then it should return correct values', async () => {
|
||||
const values = await instance.loadTagValues('key 1');
|
||||
|
||||
expect(values).toEqual(['value 1', 'value 2']);
|
||||
});
|
||||
|
||||
it('then datasource.getTagValues should be called with the correct key', async () => {
|
||||
await instance.loadTagValues('key 1');
|
||||
|
||||
expect(datasource.getTagValues).toBeCalledWith({ key: 'key 1' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when called and there is extendedOptions', () => {
|
||||
const extendedOptions = { measurement: 'default' };
|
||||
const { instance, datasource } = setup({ extendedOptions });
|
||||
|
||||
it('then it should return correct values', async () => {
|
||||
const values = await instance.loadTagValues('key 1');
|
||||
|
||||
expect(values).toEqual(['value 1', 'value 2']);
|
||||
});
|
||||
|
||||
it('then datasource.getTagValues should be called with extendedOptions and the correct key', async () => {
|
||||
await instance.loadTagValues('key 1');
|
||||
|
||||
expect(datasource.getTagValues).toBeCalledWith({ measurement: 'default', key: 'key 1' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updatePairs', () => {
|
||||
describe('when called with an empty pairs array', () => {
|
||||
describe('and called with keys', () => {
|
||||
it('then it should return correct pairs', async () => {
|
||||
// @todo remove lint disable when possible: https://github.com/typescript-eslint/typescript-eslint/issues/902
|
||||
/* eslint-disable @typescript-eslint/no-inferrable-types */
|
||||
const { instance } = setup();
|
||||
const pairs: KeyValuePair[] = [];
|
||||
const index = 0;
|
||||
const key: undefined = undefined;
|
||||
const keys: string[] = ['key 1', 'key 2'];
|
||||
const value: undefined = undefined;
|
||||
const values: undefined = undefined;
|
||||
const operator: undefined = undefined;
|
||||
/* eslint-enable @typescript-eslint/no-inferrable-types */
|
||||
|
||||
const result = instance.updatePairs(pairs, index, { key, keys, value, values, operator });
|
||||
|
||||
expect(result).toEqual([{ key: '', keys, value: '', values: [], operator: '' }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when called with an non empty pairs array', () => {
|
||||
it('then it should update correct pairs at supplied index', async () => {
|
||||
const { instance } = setup();
|
||||
const pairs: KeyValuePair[] = [
|
||||
{
|
||||
key: 'prev key 1',
|
||||
keys: ['prev key 1', 'prev key 2'],
|
||||
value: 'prev value 1',
|
||||
values: ['prev value 1', 'prev value 2'],
|
||||
operator: '=',
|
||||
},
|
||||
{
|
||||
key: 'prev key 3',
|
||||
keys: ['prev key 3', 'prev key 4'],
|
||||
value: 'prev value 3',
|
||||
values: ['prev value 3', 'prev value 4'],
|
||||
operator: '!=',
|
||||
},
|
||||
];
|
||||
const index = 1;
|
||||
const key = 'key 3';
|
||||
const keys = ['key 3', 'key 4'];
|
||||
const value = 'value 3';
|
||||
const values = ['value 3', 'value 4'];
|
||||
const operator = '=';
|
||||
|
||||
const result = instance.updatePairs(pairs, index, { key, keys, value, values, operator });
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
key: 'prev key 1',
|
||||
keys: ['prev key 1', 'prev key 2'],
|
||||
value: 'prev value 1',
|
||||
values: ['prev value 1', 'prev value 2'],
|
||||
operator: '=',
|
||||
},
|
||||
{
|
||||
key: 'key 3',
|
||||
keys: ['key 3', 'key 4'],
|
||||
value: 'value 3',
|
||||
values: ['value 3', 'value 4'],
|
||||
operator: '=',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,167 +0,0 @@
|
||||
import React from 'react';
|
||||
import { isEqual } from 'lodash';
|
||||
import { DataSourceApi, DataQuery, DataSourceJsonData } from '@grafana/data';
|
||||
import { Icon } from '@grafana/ui';
|
||||
import { AdHocFilter } from './AdHocFilter';
|
||||
export const DEFAULT_REMOVE_FILTER_VALUE = '-- remove filter --';
|
||||
|
||||
const addFilterButton = (onAddFilter: (event: React.MouseEvent) => void) => (
|
||||
<button className="gf-form-label gf-form-label--btn query-part" onClick={onAddFilter}>
|
||||
<Icon name="plus" />
|
||||
</button>
|
||||
);
|
||||
|
||||
export interface KeyValuePair {
|
||||
keys: string[];
|
||||
key: string;
|
||||
operator: string;
|
||||
value: string;
|
||||
values: string[];
|
||||
}
|
||||
|
||||
export interface Props<TQuery extends DataQuery = DataQuery, TOptions extends DataSourceJsonData = DataSourceJsonData> {
|
||||
datasource: DataSourceApi<TQuery, TOptions>;
|
||||
onPairsChanged: (pairs: KeyValuePair[]) => void;
|
||||
extendedOptions?: any;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
pairs: KeyValuePair[];
|
||||
}
|
||||
|
||||
export class AdHocFilterField<
|
||||
TQuery extends DataQuery = DataQuery,
|
||||
TOptions extends DataSourceJsonData = DataSourceJsonData
|
||||
> extends React.PureComponent<Props<TQuery, TOptions>, State> {
|
||||
state: State = { pairs: [] };
|
||||
|
||||
componentDidUpdate(prevProps: Props<TQuery, TOptions>) {
|
||||
if (isEqual(prevProps.extendedOptions, this.props.extendedOptions) === false) {
|
||||
const pairs: any[] = [];
|
||||
|
||||
this.setState({ pairs }, () => this.props.onPairsChanged(pairs));
|
||||
}
|
||||
}
|
||||
|
||||
loadTagKeys = async () => {
|
||||
const { datasource, extendedOptions } = this.props;
|
||||
const options = extendedOptions || {};
|
||||
const tagKeys = datasource.getTagKeys ? await datasource.getTagKeys(options) : [];
|
||||
const keys = tagKeys.map((tagKey) => tagKey.text);
|
||||
|
||||
return keys;
|
||||
};
|
||||
|
||||
loadTagValues = async (key: string) => {
|
||||
const { datasource, extendedOptions } = this.props;
|
||||
const options = extendedOptions || {};
|
||||
const tagValues = datasource.getTagValues ? await datasource.getTagValues({ ...options, key }) : [];
|
||||
const values = tagValues.map((tagValue) => tagValue.text);
|
||||
|
||||
return values;
|
||||
};
|
||||
|
||||
updatePairs(pairs: KeyValuePair[], index: number, pair: Partial<KeyValuePair>) {
|
||||
if (pairs.length === 0) {
|
||||
return [
|
||||
{
|
||||
key: pair.key || '',
|
||||
keys: pair.keys || [],
|
||||
operator: pair.operator || '',
|
||||
value: pair.value || '',
|
||||
values: pair.values || [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const newPairs: KeyValuePair[] = [];
|
||||
for (let pairIndex = 0; pairIndex < pairs.length; pairIndex++) {
|
||||
const newPair = pairs[pairIndex];
|
||||
if (index === pairIndex) {
|
||||
newPairs.push({
|
||||
...newPair,
|
||||
key: pair.key || newPair.key,
|
||||
value: pair.value || newPair.value,
|
||||
operator: pair.operator || newPair.operator,
|
||||
keys: pair.keys || newPair.keys,
|
||||
values: pair.values || newPair.values,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
newPairs.push(newPair);
|
||||
}
|
||||
|
||||
return newPairs;
|
||||
}
|
||||
|
||||
onKeyChanged = (index: number) => async (key: string) => {
|
||||
if (key !== DEFAULT_REMOVE_FILTER_VALUE) {
|
||||
const { onPairsChanged } = this.props;
|
||||
const values = await this.loadTagValues(key);
|
||||
const pairs = this.updatePairs(this.state.pairs, index, { key, values });
|
||||
|
||||
this.setState({ pairs }, () => onPairsChanged(pairs));
|
||||
} else {
|
||||
this.onRemoveFilter(index);
|
||||
}
|
||||
};
|
||||
|
||||
onValueChanged = (index: number) => (value: string) => {
|
||||
const pairs = this.updatePairs(this.state.pairs, index, { value });
|
||||
|
||||
this.setState({ pairs }, () => this.props.onPairsChanged(pairs));
|
||||
};
|
||||
|
||||
onOperatorChanged = (index: number) => (operator: string) => {
|
||||
const pairs = this.updatePairs(this.state.pairs, index, { operator });
|
||||
|
||||
this.setState({ pairs }, () => this.props.onPairsChanged(pairs));
|
||||
};
|
||||
|
||||
onAddFilter = async () => {
|
||||
const keys = await this.loadTagKeys();
|
||||
const pairs = this.state.pairs.concat(this.updatePairs([], 0, { keys }));
|
||||
|
||||
this.setState({ pairs }, () => this.props.onPairsChanged(pairs));
|
||||
};
|
||||
|
||||
onRemoveFilter = async (index: number) => {
|
||||
const pairs = this.state.pairs.reduce((allPairs, pair, pairIndex) => {
|
||||
if (pairIndex === index) {
|
||||
return allPairs;
|
||||
}
|
||||
return allPairs.concat(pair);
|
||||
}, [] as KeyValuePair[]);
|
||||
|
||||
this.setState({ pairs });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { pairs } = this.state;
|
||||
return (
|
||||
<>
|
||||
{pairs.length < 1 && addFilterButton(this.onAddFilter)}
|
||||
{pairs.map((pair, index) => {
|
||||
const adHocKey = `adhoc-filter-${index}-${pair.key}-${pair.value}`;
|
||||
return (
|
||||
<div className="align-items-center flex-grow-1" key={adHocKey}>
|
||||
<AdHocFilter
|
||||
keys={[DEFAULT_REMOVE_FILTER_VALUE].concat(pair.keys)}
|
||||
values={pair.values}
|
||||
initialKey={pair.key}
|
||||
initialOperator={pair.operator}
|
||||
initialValue={pair.value}
|
||||
onKeyChanged={this.onKeyChanged(index)}
|
||||
onOperatorChanged={this.onOperatorChanged(index)}
|
||||
onValueChanged={this.onValueChanged(index)}
|
||||
/>
|
||||
{index < pairs.length - 1 && <span> AND </span>}
|
||||
{index === pairs.length - 1 && addFilterButton(this.onAddFilter)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { InfluxLogsQueryField, pairsAreValid } from './InfluxLogsQueryField';
|
||||
import { InfluxDatasourceMock } from '../datasource.mock';
|
||||
import InfluxDatasource from '../datasource';
|
||||
import { InfluxQuery } from '../types';
|
||||
import { ButtonCascader } from '@grafana/ui';
|
||||
import { KeyValuePair } from '../../../../features/explore/AdHocFilterField';
|
||||
|
||||
describe('pairsAreValid()', () => {
|
||||
describe('when all pairs are fully defined', () => {
|
||||
it('should return true', () => {
|
||||
const pairs = [
|
||||
{
|
||||
key: 'a',
|
||||
operator: '=',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
key: 'b',
|
||||
operator: '!=',
|
||||
value: '2',
|
||||
},
|
||||
];
|
||||
|
||||
expect(pairsAreValid(pairs as any)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when no pairs are defined at all', () => {
|
||||
it('should return true', () => {
|
||||
expect(pairsAreValid([])).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when pairs are undefined', () => {
|
||||
it('should return true', () => {
|
||||
expect(pairsAreValid((undefined as unknown) as KeyValuePair[])).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when one or more pairs are only partially defined', () => {
|
||||
it('should return false', () => {
|
||||
const pairs = [
|
||||
{
|
||||
key: 'a',
|
||||
operator: undefined,
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
key: 'b',
|
||||
operator: '!=',
|
||||
value: '2',
|
||||
},
|
||||
];
|
||||
|
||||
expect(pairsAreValid(pairs as any)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('InfluxLogsQueryField', () => {
|
||||
it('should load and show correct measurements and fields in cascader', async () => {
|
||||
const wrapper = getInfluxLogsQueryField();
|
||||
// Looks strange but we do async stuff in didMount and this will push the stack at the end of eval loop, effectively
|
||||
// waiting for the didMount to finish.
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
wrapper.update();
|
||||
const cascader = wrapper.find(ButtonCascader);
|
||||
expect(cascader.prop('options')).toEqual([
|
||||
{ label: 'logs', value: 'logs', children: [{ label: 'description', value: 'description', children: [] }] },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
function getInfluxLogsQueryField(props?: any) {
|
||||
const datasource: InfluxDatasource = new InfluxDatasourceMock(
|
||||
props?.measurements || {
|
||||
logs: [{ name: 'description', type: 'string' }],
|
||||
}
|
||||
) as any;
|
||||
|
||||
const defaultProps = {
|
||||
datasource,
|
||||
history: [] as any[],
|
||||
onRunQuery: () => {},
|
||||
onChange: (query: InfluxQuery) => {},
|
||||
query: {
|
||||
refId: '',
|
||||
} as InfluxQuery,
|
||||
};
|
||||
return mount(
|
||||
<InfluxLogsQueryField
|
||||
{...{
|
||||
...defaultProps,
|
||||
...props,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ExploreQueryFieldProps } from '@grafana/data';
|
||||
import { ButtonCascader, CascaderOption } from '@grafana/ui';
|
||||
|
||||
import InfluxQueryModel from '../influx_query_model';
|
||||
import { AdHocFilterField, KeyValuePair } from 'app/features/explore/AdHocFilterField';
|
||||
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime';
|
||||
import InfluxDatasource from '../datasource';
|
||||
import { InfluxQueryBuilder } from '../query_builder';
|
||||
import { InfluxOptions, InfluxQuery } from '../types';
|
||||
|
||||
export interface Props extends ExploreQueryFieldProps<InfluxDatasource, InfluxQuery, InfluxOptions> {}
|
||||
|
||||
export interface State {
|
||||
measurements: CascaderOption[];
|
||||
measurement: string | null;
|
||||
field: string | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface ChooserOptions {
|
||||
measurement: string | null;
|
||||
field: string | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// Helper function for determining if a collection of pairs are valid
|
||||
// where a valid pair is either fully defined, or not defined at all, but not partially defined
|
||||
export function pairsAreValid(pairs: KeyValuePair[]) {
|
||||
return (
|
||||
!pairs ||
|
||||
pairs.every((pair) => {
|
||||
const allDefined = !!(pair.key && pair.operator && pair.value);
|
||||
const allEmpty = pair.key === undefined && pair.operator === undefined && pair.value === undefined;
|
||||
return allDefined || allEmpty;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function getChooserText({ measurement, field, error }: ChooserOptions): string {
|
||||
if (error) {
|
||||
return '(No measurement found)';
|
||||
}
|
||||
if (measurement) {
|
||||
return `Measurements (${measurement}/${field})`;
|
||||
}
|
||||
return 'Measurements';
|
||||
}
|
||||
|
||||
export class InfluxLogsQueryField extends React.PureComponent<Props, State> {
|
||||
templateSrv: TemplateSrv = getTemplateSrv();
|
||||
state: State = {
|
||||
measurements: [],
|
||||
measurement: null,
|
||||
field: null,
|
||||
error: null,
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
const { datasource } = this.props;
|
||||
try {
|
||||
const queryBuilder = new InfluxQueryBuilder({ measurement: '', tags: [] }, datasource.database);
|
||||
const measureMentsQuery = queryBuilder.buildExploreQuery('MEASUREMENTS');
|
||||
const influxMeasurements = await datasource.metricFindQuery(measureMentsQuery);
|
||||
|
||||
const measurements = [];
|
||||
for (let index = 0; index < influxMeasurements.length; index++) {
|
||||
const measurementObj = influxMeasurements[index];
|
||||
const queryBuilder = new InfluxQueryBuilder(
|
||||
{ measurement: measurementObj.text, tags: [] },
|
||||
datasource.database
|
||||
);
|
||||
const fieldsQuery = queryBuilder.buildExploreQuery('FIELDS');
|
||||
const influxFields = await datasource.metricFindQuery(fieldsQuery);
|
||||
const fields: any[] = influxFields.map((field: any): any => ({
|
||||
label: field.text,
|
||||
value: field.text,
|
||||
children: [],
|
||||
}));
|
||||
measurements.push({
|
||||
label: measurementObj.text,
|
||||
value: measurementObj.text,
|
||||
children: fields,
|
||||
});
|
||||
}
|
||||
this.setState({ measurements });
|
||||
} catch (error) {
|
||||
const message = error && error.message ? error.message : error;
|
||||
console.error(error);
|
||||
this.setState({ error: message });
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
if (prevProps.query.measurement && !this.props.query.measurement) {
|
||||
this.setState({ measurement: null, field: null });
|
||||
}
|
||||
}
|
||||
|
||||
onMeasurementsChange = async (values: string[]) => {
|
||||
const { query } = this.props;
|
||||
const measurement = values[0];
|
||||
const field = values[1];
|
||||
|
||||
this.setState({ measurement, field }, () => {
|
||||
this.onPairsChanged((query as any).tags);
|
||||
});
|
||||
};
|
||||
|
||||
onPairsChanged = (pairs: KeyValuePair[]) => {
|
||||
const { query } = this.props;
|
||||
const { measurement, field } = this.state;
|
||||
const queryModel = new InfluxQueryModel(
|
||||
{
|
||||
...query,
|
||||
resultFormat: 'table',
|
||||
groupBy: [],
|
||||
select: [[{ type: 'field', params: [field ?? ''] }]],
|
||||
tags: pairs,
|
||||
limit: '1000',
|
||||
measurement: measurement ?? '',
|
||||
},
|
||||
this.templateSrv
|
||||
);
|
||||
|
||||
this.props.onChange(queryModel.target);
|
||||
|
||||
// Only run the query if measurement & field are set, and there are no invalid pairs
|
||||
if (measurement && field && pairsAreValid(pairs)) {
|
||||
this.props.onRunQuery();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { datasource } = this.props;
|
||||
const { measurements, measurement, field, error } = this.state;
|
||||
const cascadeText = getChooserText({ measurement, field, error });
|
||||
const hasMeasurement = measurements && measurements.length > 0;
|
||||
|
||||
return (
|
||||
<div className="gf-form-inline gf-form-inline--nowrap">
|
||||
<div className="gf-form flex-shrink-0">
|
||||
<ButtonCascader
|
||||
options={measurements}
|
||||
disabled={!hasMeasurement}
|
||||
value={[measurement ?? '', field ?? '']}
|
||||
onChange={this.onMeasurementsChange}
|
||||
>
|
||||
{cascadeText}
|
||||
</ButtonCascader>
|
||||
</div>
|
||||
<div className="flex-shrink-1 flex-flow-column-nowrap">
|
||||
{measurement && (
|
||||
<AdHocFilterField
|
||||
onPairsChanged={this.onPairsChanged}
|
||||
datasource={datasource}
|
||||
extendedOptions={{ measurement }}
|
||||
/>
|
||||
)}
|
||||
{error ? (
|
||||
<span className="gf-form-label gf-form-label--transparent gf-form-label--error m-l-2">{error}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user