[TSVB] Add Include and Exclude fields to the Terms group (#34153)

* Add Include and Exclude fields to the Terms group

* Change *clude to *cludeLabel

* Add the include and exclude parametrs to the server side

* Remove an unnecessary prop

* Use destructring

* Change back a variable

* Destruct lodash

* Fix input filling

* Add type checking to inputs

* Add constants to field types

* Make a snapshot of the SplitByTermsUI component

* Move constants to common
This commit is contained in:
Artyom Gospodarsky 2019-04-09 12:12:05 +03:00 committed by GitHub
parent 0eff2e15f5
commit 11e9d88ba9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 370 additions and 20 deletions

View file

@ -0,0 +1,26 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export const FIELD_TYPES = {
BOOLEAN: 'boolean',
DATE: 'date',
GEO: 'geo_point',
NUMBER: 'number',
STRING: 'string',
};

View file

@ -0,0 +1,220 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js <SplitByTermsUI /> should render and match a snapshot 1`] = `
<div>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
id="42"
label={
<FormattedMessage
defaultMessage="Group by"
id="tsvb.splits.terms.groupByLabel"
values={Object {}}
/>
}
labelType="label"
>
<InjectIntl(GroupBySelectUi)
onChange={[Function]}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
id="42"
label={
<FormattedMessage
defaultMessage="By"
id="tsvb.splits.terms.byLabel"
values={Object {}}
/>
}
labelType="label"
>
<InjectIntl(FieldSelectUi)
fields={
Object {
"kibana_sample_data_flights": Array [
Object {
"aggregatable": true,
"name": "OriginCityName",
"readFromDocValues": true,
"searchable": true,
"type": "string",
},
],
}
}
indexPattern="kibana_sample_data_flights"
onChange={[Function]}
value="OriginCityName"
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
id="42"
label={
<FormattedMessage
defaultMessage="Include"
id="tsvb.splits.terms.includeLabel"
values={Object {}}
/>
}
labelType="label"
>
<EuiFieldText
compressed={false}
fullWidth={false}
isLoading={false}
onChange={[Function]}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
id="42"
label={
<FormattedMessage
defaultMessage="Exclude"
id="tsvb.splits.terms.excludeLabel"
values={Object {}}
/>
}
labelType="label"
>
<EuiFieldText
compressed={false}
fullWidth={false}
isLoading={false}
onChange={[Function]}
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
id="42"
label={
<FormattedMessage
defaultMessage="Top"
id="tsvb.splits.terms.topLabel"
values={Object {}}
/>
}
labelType="label"
>
<EuiFieldNumber
compressed={false}
fullWidth={false}
isLoading={false}
onChange={[Function]}
value={10}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
id="42"
label={
<FormattedMessage
defaultMessage="Order by"
id="tsvb.splits.terms.orderByLabel"
values={Object {}}
/>
}
labelType="label"
>
<InjectIntl(MetricSelectUi)
additionalOptions={
Array [
Object {
"label": undefined,
"value": "_count",
},
Object {
"label": undefined,
"value": "_key",
},
]
}
clearable={false}
onChange={[Function]}
restrict="basic"
value="_count"
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
id="42"
label={
<FormattedMessage
defaultMessage="Direction"
id="tsvb.splits.terms.directionLabel"
values={Object {}}
/>
}
labelType="label"
>
<EuiComboBox
compressed={false}
fullWidth={false}
isClearable={false}
onChange={[Function]}
options={
Array [
Object {
"label": undefined,
"value": "desc",
},
Object {
"label": undefined,
"value": "asc",
},
]
}
selectedOptions={
Array [
Object {
"label": undefined,
"value": "desc",
},
]
}
singleSelection={
Object {
"asPlainText": true,
}
}
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
</div>
`;

View file

@ -19,21 +19,23 @@
import PropTypes from 'prop-types';
import React from 'react';
import { get, find } from 'lodash';
import GroupBySelect from './group_by_select';
import createTextHandler from '../lib/create_text_handler';
import createSelectHandler from '../lib/create_select_handler';
import FieldSelect from '../aggs/field_select';
import MetricSelect from '../aggs/metric_select';
import { htmlIdGenerator, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiFieldNumber, EuiComboBox, EuiSpacer } from '@elastic/eui';
import { htmlIdGenerator, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiFieldNumber, EuiComboBox, EuiFieldText } from '@elastic/eui';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { FIELD_TYPES } from '../../../common/field_types';
const SplitByTermsUi = props => {
const DEFAULTS = { terms_direction: 'desc', terms_size: 10, terms_order_by: '_count' };
export const SplitByTermsUI = ({ onChange, indexPattern, intl, model: seriesModel, fields }) => {
const htmlId = htmlIdGenerator();
const handleTextChange = createTextHandler(props.onChange);
const handleSelectChange = createSelectHandler(props.onChange);
const { indexPattern, intl } = props;
const defaults = { terms_direction: 'desc', terms_size: 10, terms_order_by: '_count' };
const model = { ...defaults, ...props.model };
const handleTextChange = createTextHandler(onChange);
const handleSelectChange = createSelectHandler(onChange);
const model = { ...DEFAULTS, ...seriesModel };
const { metrics } = model;
const defaultCount = {
value: '_count',
@ -57,10 +59,12 @@ const SplitByTermsUi = props => {
const selectedDirectionOption = dirOptions.find(option => {
return model.terms_direction === option.value;
});
const selectedField = find(fields[indexPattern], ({ name }) => name === model.terms_field);
const selectedFieldType = get(selectedField, 'type');
return (
<div>
<EuiFlexGroup alignItems="center">
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
id={htmlId('group')}
@ -88,15 +92,40 @@ const SplitByTermsUi = props => {
indexPattern={indexPattern}
onChange={handleSelectChange('terms_field')}
value={model.terms_field}
fields={props.fields}
fields={fields}
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer />
{selectedFieldType === FIELD_TYPES.STRING && (
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
id={htmlId('include')}
label={(<FormattedMessage
id="tsvb.splits.terms.includeLabel"
defaultMessage="Include"
/>)}
>
<EuiFieldText value={model.terms_include} onChange={handleTextChange('terms_include')} />
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
id={htmlId('exclude')}
label={(<FormattedMessage
id="tsvb.splits.terms.excludeLabel"
defaultMessage="Exclude"
/>)}
>
<EuiFieldText value={model.terms_exclude} onChange={handleTextChange('terms_exclude')} />
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
)}
<EuiFlexGroup alignItems="center">
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
id={htmlId('top')}
@ -152,11 +181,12 @@ const SplitByTermsUi = props => {
);
};
SplitByTermsUi.propTypes = {
SplitByTermsUI.propTypes = {
intl: PropTypes.object,
model: PropTypes.object,
onChange: PropTypes.func,
indexPattern: PropTypes.string,
fields: PropTypes.object
};
export const SplitByTerms = injectI18n(SplitByTermsUi);
export const SplitByTerms = injectI18n(SplitByTermsUI);

View file

@ -0,0 +1,68 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { SplitByTermsUI } from './terms';
jest.mock('@elastic/eui', () => ({
htmlIdGenerator: jest.fn(() => () => '42'),
EuiFlexGroup: require.requireActual('@elastic/eui').EuiFlexGroup,
EuiFlexItem: require.requireActual('@elastic/eui').EuiFlexItem,
EuiFormRow: require.requireActual('@elastic/eui').EuiFormRow,
EuiFieldNumber: require.requireActual('@elastic/eui').EuiFieldNumber,
EuiComboBox: require.requireActual('@elastic/eui').EuiComboBox,
EuiFieldText: require.requireActual('@elastic/eui').EuiFieldText,
}));
describe('src/legacy/core_plugins/metrics/public/components/splits/terms.test.js', () => {
let props;
beforeEach(() => {
props = {
intl: {
formatMessage: jest.fn(),
},
model: {
terms_field: 'OriginCityName'
},
onChange: jest.fn(),
indexPattern: 'kibana_sample_data_flights',
fields: {
'kibana_sample_data_flights': [
{
aggregatable: true,
name: 'OriginCityName',
readFromDocValues: true,
searchable: true,
type: 'string'
}
]
},
};
});
describe('<SplitByTermsUI />', () => {
test('should render and match a snapshot', () => {
const wrapper = shallow(<SplitByTermsUI {...props} />);
expect(wrapper).toMatchSnapshot();
});
});
});

View file

@ -17,7 +17,7 @@
* under the License.
*/
import _ from 'lodash';
import { set } from 'lodash';
import basicAggs from '../../../../../common/basic_aggs';
import getBucketsPath from '../../helpers/get_buckets_path';
import bucketTransform from '../../helpers/bucket_transform';
@ -26,20 +26,26 @@ export default function splitByTerm(req, panel, series) {
return next => doc => {
if (series.split_mode === 'terms' && series.terms_field) {
const direction = series.terms_direction || 'desc';
_.set(doc, `aggs.${series.id}.terms.field`, series.terms_field);
_.set(doc, `aggs.${series.id}.terms.size`, series.terms_size);
const metric = series.metrics.find(item => item.id === series.terms_order_by);
set(doc, `aggs.${series.id}.terms.field`, series.terms_field);
set(doc, `aggs.${series.id}.terms.size`, series.terms_size);
if (series.terms_include) {
set(doc, `aggs.${series.id}.terms.include`, series.terms_include);
}
if (series.terms_exclude) {
set(doc, `aggs.${series.id}.terms.exclude`, series.terms_exclude);
}
if (metric && metric.type !== 'count' && ~basicAggs.indexOf(metric.type)) {
const sortAggKey = `${series.terms_order_by}-SORT`;
const fn = bucketTransform[metric.type];
const bucketPath = getBucketsPath(series.terms_order_by, series.metrics)
.replace(series.terms_order_by, sortAggKey);
_.set(doc, `aggs.${series.id}.terms.order`, { [bucketPath]: direction });
_.set(doc, `aggs.${series.id}.aggs`, { [sortAggKey]: fn(metric) });
set(doc, `aggs.${series.id}.terms.order`, { [bucketPath]: direction });
set(doc, `aggs.${series.id}.aggs`, { [sortAggKey]: fn(metric) });
} else if (['_key', '_count'].includes(series.terms_order_by)) {
_.set(doc, `aggs.${series.id}.terms.order`, { [series.terms_order_by]: direction });
set(doc, `aggs.${series.id}.terms.order`, { [series.terms_order_by]: direction });
} else {
_.set(doc, `aggs.${series.id}.terms.order`, { _count: direction });
set(doc, `aggs.${series.id}.terms.order`, { _count: direction });
}
}
return next(doc);