[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:
parent
0eff2e15f5
commit
11e9d88ba9
26
src/legacy/core_plugins/metrics/common/field_types.js
Normal file
26
src/legacy/core_plugins/metrics/common/field_types.js
Normal 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',
|
||||
};
|
220
src/legacy/core_plugins/metrics/public/components/splits/__snapshots__/terms.test.js.snap
generated
Normal file
220
src/legacy/core_plugins/metrics/public/components/splits/__snapshots__/terms.test.js.snap
generated
Normal 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>
|
||||
`;
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue