mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
* Allow non numeric fields in Top Hits in TSVB * fix build * fix pr comment
This commit is contained in:
parent
fcc2520822
commit
8a98a89cbf
21 changed files with 233 additions and 193 deletions
|
@ -22,8 +22,8 @@ import getLastValue from '../get_last_value';
|
|||
|
||||
describe('getLastValue(data)', () => {
|
||||
|
||||
it('returns zero if data is not array', () => {
|
||||
expect(getLastValue('foo')).to.equal(0);
|
||||
it('returns data if data is not array', () => {
|
||||
expect(getLastValue('foo')).to.equal('foo');
|
||||
});
|
||||
|
||||
it('returns the last value', () => {
|
||||
|
@ -33,10 +33,10 @@ describe('getLastValue(data)', () => {
|
|||
|
||||
it('returns the second to last value if the last value is null (default)', () => {
|
||||
const data = [[1, 4], [2, null]];
|
||||
expect(getLastValue(data)).to.equal(0);
|
||||
expect(getLastValue(data)).to.equal(4);
|
||||
});
|
||||
|
||||
it('returns the zero if second to last is null (default)', () => {
|
||||
it('returns 0 if second to last is not defined (default)', () => {
|
||||
const data = [[1, null], [2, null]];
|
||||
expect(getLastValue(data)).to.equal(0);
|
||||
});
|
||||
|
@ -45,7 +45,5 @@ describe('getLastValue(data)', () => {
|
|||
const data = [[1, 4], [2, null], [3, null]];
|
||||
expect(getLastValue(data, 3)).to.equal(4);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -17,25 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
export default function byType(type) {
|
||||
return (field) => {
|
||||
switch (type) {
|
||||
case 'numeric':
|
||||
return _.includes([
|
||||
'number'
|
||||
], field.type);
|
||||
case 'string':
|
||||
return _.includes([
|
||||
'string', 'keyword', 'text'
|
||||
], field.type);
|
||||
case 'date':
|
||||
return _.includes([
|
||||
'date'
|
||||
], field.type);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const ES_TYPES = {
|
||||
NUMBER: 'number',
|
||||
STRING: 'string',
|
||||
KEYWORD: 'keyword',
|
||||
TEXT: 'text',
|
||||
DATE: 'date',
|
||||
};
|
|
@ -17,24 +17,20 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
export default (data, lookback = 1) => {
|
||||
if (_.isNumber(data)) return data;
|
||||
if (!Array.isArray(data)) return 0;
|
||||
// First try the last value
|
||||
const last = data[data.length - 1];
|
||||
const lastValue = Array.isArray(last) && last[1];
|
||||
if (lastValue) return lastValue;
|
||||
import { isArray, findLast } from 'lodash';
|
||||
|
||||
const DEFAULT_VALUE = 0;
|
||||
|
||||
export default (data, defaultValue = DEFAULT_VALUE) => {
|
||||
if (!isArray(data)) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const extractValue = data => data && data[1] || null;
|
||||
|
||||
// If the last value is zero or null because of a partial bucket or
|
||||
// some kind of timeshift weirdness we will show the second to last.
|
||||
let lookbackCounter = 1;
|
||||
let value;
|
||||
while (lookback > lookbackCounter && !value) {
|
||||
const next = data[data.length - ++lookbackCounter];
|
||||
value = _.isArray(next) && next[1] || 0;
|
||||
}
|
||||
return value || 0;
|
||||
const lastValid = findLast(data, item => extractValue(item));
|
||||
|
||||
return extractValue(lastValid) || defaultValue;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ export const METRIC_TYPES = {
|
|||
STD_DEVIATION: 'std_deviation',
|
||||
VARIANCE: 'variance',
|
||||
SUM_OF_SQUARES: 'sum_of_squares',
|
||||
CARDINALITY: 'cardinality',
|
||||
};
|
||||
|
||||
export const EXTENDED_STATS_TYPES = [
|
||||
|
|
27
src/legacy/core_plugins/metrics/common/panel_types.js
Normal file
27
src/legacy/core_plugins/metrics/common/panel_types.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 PANEL_TYPES = {
|
||||
TABLE: 'table',
|
||||
GAUGE: 'gauge',
|
||||
MARKDOWN: 'markdown',
|
||||
TOP_N: 'top_n',
|
||||
TIMESERIES: 'timeseries',
|
||||
METRIC: 'metric',
|
||||
};
|
|
@ -22,9 +22,12 @@ import React from 'react';
|
|||
import {
|
||||
EuiComboBox,
|
||||
} from '@elastic/eui';
|
||||
import generateByTypeFilter from '../lib/generate_by_type_filter';
|
||||
import { injectI18n } from '@kbn/i18n/react';
|
||||
import { isFieldEnabled } from '../../lib/check_ui_restrictions';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
const isFieldTypeEnabled = (fieldRestrictions, fieldType) =>
|
||||
fieldRestrictions.length ? fieldRestrictions.includes(fieldType) : true;
|
||||
|
||||
function FieldSelectUi({
|
||||
type,
|
||||
|
@ -34,7 +37,6 @@ function FieldSelectUi({
|
|||
onChange,
|
||||
disabled,
|
||||
restrict,
|
||||
intl,
|
||||
uiRestrictions,
|
||||
...rest
|
||||
}) {
|
||||
|
@ -43,18 +45,38 @@ function FieldSelectUi({
|
|||
return null;
|
||||
}
|
||||
|
||||
const typeFilter = generateByTypeFilter(restrict);
|
||||
const options = (fields[indexPattern] || [])
|
||||
.filter(field => typeFilter(field) && isFieldEnabled(field.name, type, uiRestrictions))
|
||||
.map(field => ({ label: field.name, value: field.name }));
|
||||
const selectedOptions = [];
|
||||
const options = Object.values((fields[indexPattern] || []).reduce((acc, field) => {
|
||||
if (isFieldTypeEnabled(restrict, field.type) && isFieldEnabled(field.name, type, uiRestrictions)) {
|
||||
const item = {
|
||||
label: field.name,
|
||||
value: field.name
|
||||
};
|
||||
|
||||
const selectedOption = options.find(option => value === option.value);
|
||||
const selectedOptions = selectedOption ? [selectedOption] : [];
|
||||
if (acc[field.type]) {
|
||||
acc[field.type].options.push(item);
|
||||
} else {
|
||||
acc[field.type] = {
|
||||
options: [item],
|
||||
label: field.type,
|
||||
};
|
||||
}
|
||||
|
||||
if (value === item.value) {
|
||||
selectedOptions.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {}));
|
||||
|
||||
if (onChange && value && !selectedOptions.length) {
|
||||
onChange();
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiComboBox
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'tsvb.fieldSelect.selectFieldPlaceholder',
|
||||
placeholder={i18n.translate('tsvb.fieldSelect.selectFieldPlaceholder', {
|
||||
defaultMessage: 'Select field...',
|
||||
})}
|
||||
isDisabled={disabled}
|
||||
|
@ -70,7 +92,7 @@ function FieldSelectUi({
|
|||
FieldSelectUi.defaultProps = {
|
||||
indexPattern: '*',
|
||||
disabled: false,
|
||||
restrict: 'none',
|
||||
restrict: [],
|
||||
};
|
||||
|
||||
FieldSelectUi.propTypes = {
|
||||
|
@ -79,7 +101,7 @@ FieldSelectUi.propTypes = {
|
|||
id: PropTypes.string,
|
||||
indexPattern: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
restrict: PropTypes.string,
|
||||
restrict: PropTypes.array,
|
||||
type: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
uiRestrictions: PropTypes.object,
|
||||
|
|
|
@ -35,6 +35,8 @@ import {
|
|||
EuiFormRow,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ES_TYPES } from '../../../common/es_types';
|
||||
import { METRIC_TYPES } from '../../../common/metric_types';
|
||||
|
||||
export const FilterRatioAgg = props => {
|
||||
const {
|
||||
|
@ -57,7 +59,7 @@ export const FilterRatioAgg = props => {
|
|||
const model = { ...defaults, ...props.model };
|
||||
const htmlId = htmlIdGenerator();
|
||||
|
||||
const restrictMode = model.metric_agg === 'cardinality' ? 'none' : 'numeric';
|
||||
const restrictFields = model.metric_agg === METRIC_TYPES.CARDINALITY ? [] : [ES_TYPES.NUMBER];
|
||||
|
||||
return (
|
||||
<AggRow
|
||||
|
@ -149,7 +151,7 @@ export const FilterRatioAgg = props => {
|
|||
<FieldSelect
|
||||
fields={fields}
|
||||
type={model.metric_agg}
|
||||
restrict={restrictMode}
|
||||
restrict={restrictFields}
|
||||
indexPattern={indexPattern}
|
||||
value={model.field}
|
||||
onChange={handleSelectChange('field')}
|
||||
|
|
|
@ -39,11 +39,14 @@ import {
|
|||
EuiFormRow,
|
||||
} from '@elastic/eui';
|
||||
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ES_TYPES } from '../../../common/es_types';
|
||||
|
||||
const newPercentile = (opts) => {
|
||||
return _.assign({ id: uuid.v1(), mode: 'line', shade: 0.2 }, opts);
|
||||
};
|
||||
|
||||
const RESTRICT_FIELDS = [ES_TYPES.NUMBER];
|
||||
|
||||
class PercentilesUi extends Component {
|
||||
|
||||
handleTextChange(item, name) {
|
||||
|
@ -257,7 +260,7 @@ class PercentileAgg extends Component { // eslint-disable-line react/no-multi-co
|
|||
<FieldSelect
|
||||
fields={fields}
|
||||
type={model.type}
|
||||
restrict="numeric"
|
||||
restrict={RESTRICT_FIELDS}
|
||||
indexPattern={indexPattern}
|
||||
value={model.field}
|
||||
onChange={handleSelectChange('field')}
|
||||
|
|
|
@ -36,6 +36,9 @@ import {
|
|||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ES_TYPES } from '../../../../common/es_types';
|
||||
|
||||
const RESTRICT_FIELDS = [ES_TYPES.NUMBER];
|
||||
|
||||
export const PercentileRankAgg = props => {
|
||||
const { series, panel, fields } = props;
|
||||
|
@ -89,7 +92,7 @@ export const PercentileRankAgg = props => {
|
|||
<FieldSelect
|
||||
fields={fields}
|
||||
type={model.type}
|
||||
restrict="numeric"
|
||||
restrict={RESTRICT_FIELDS}
|
||||
indexPattern={indexPattern}
|
||||
value={model.field}
|
||||
onChange={handleSelectChange('field')}
|
||||
|
|
|
@ -26,15 +26,14 @@ import createChangeHandler from '../lib/create_change_handler';
|
|||
import createSelectHandler from '../lib/create_select_handler';
|
||||
import { htmlIdGenerator, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiFormLabel } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ES_TYPES } from '../../../common/es_types';
|
||||
import { METRIC_TYPES } from '../../../common/metric_types';
|
||||
|
||||
function StandardAgg(props) {
|
||||
const { model, panel, series, fields, uiRestrictions } = props;
|
||||
const handleChange = createChangeHandler(props.onChange, model);
|
||||
const handleSelectChange = createSelectHandler(handleChange);
|
||||
let restrict = 'numeric';
|
||||
if (model.type === 'cardinality') {
|
||||
restrict = 'none';
|
||||
}
|
||||
const restrictFields = model.type === METRIC_TYPES.CARDINALITY ? [] : [ES_TYPES.NUMBER];
|
||||
|
||||
const indexPattern = series.override_index_pattern && series.series_index_pattern || panel.index_pattern;
|
||||
const htmlId = htmlIdGenerator();
|
||||
|
@ -81,7 +80,7 @@ function StandardAgg(props) {
|
|||
<FieldSelect
|
||||
fields={fields}
|
||||
type={model.type}
|
||||
restrict={restrict}
|
||||
restrict={restrictFields}
|
||||
indexPattern={indexPattern}
|
||||
value={model.field}
|
||||
onChange={handleSelectChange('field')}
|
||||
|
|
|
@ -35,6 +35,9 @@ import {
|
|||
EuiFormRow,
|
||||
} from '@elastic/eui';
|
||||
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ES_TYPES } from '../../../common/es_types';
|
||||
|
||||
const RESTRICT_FIELDS = [ES_TYPES.NUMBER];
|
||||
|
||||
const StandardDeviationAggUi = props => {
|
||||
const { series, panel, fields, intl } = props;
|
||||
|
@ -108,7 +111,7 @@ const StandardDeviationAggUi = props => {
|
|||
<FieldSelect
|
||||
fields={fields}
|
||||
type={model.type}
|
||||
restrict="numeric"
|
||||
restrict={RESTRICT_FIELDS}
|
||||
indexPattern={indexPattern}
|
||||
value={model.field}
|
||||
onChange={handleSelectChange('field')}
|
||||
|
|
|
@ -21,6 +21,7 @@ import React from 'react';
|
|||
import AggRow from './agg_row';
|
||||
import AggSelect from './agg_select';
|
||||
import FieldSelect from './field_select';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import createChangeHandler from '../lib/create_change_handler';
|
||||
import createSelectHandler from '../lib/create_select_handler';
|
||||
import createTextHandler from '../lib/create_text_handler';
|
||||
|
@ -34,12 +35,80 @@ import {
|
|||
EuiFormRow,
|
||||
} from '@elastic/eui';
|
||||
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ES_TYPES } from '../../../common/es_types';
|
||||
import { PANEL_TYPES } from '../../../common/panel_types';
|
||||
|
||||
const isFieldTypeEnabled = (fieldRestrictions, fieldType) =>
|
||||
fieldRestrictions.length ? fieldRestrictions.includes(fieldType) : true;
|
||||
|
||||
const getAggWithOptions = (field = {}, fieldTypesRestriction) => {
|
||||
if (isFieldTypeEnabled(fieldTypesRestriction, field.type)) {
|
||||
switch (field.type) {
|
||||
case ES_TYPES.NUMBER:
|
||||
return [
|
||||
{
|
||||
label: i18n.translate('tsvb.topHit.aggWithOptions.averageLabel', {
|
||||
defaultMessage: 'Avg',
|
||||
}),
|
||||
value: 'avg',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.topHit.aggWithOptions.maxLabel', {
|
||||
defaultMessage: 'Max',
|
||||
}),
|
||||
value: 'max',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.topHit.aggWithOptions.minLabel', {
|
||||
defaultMessage: 'Min',
|
||||
}),
|
||||
value: 'min',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.topHit.aggWithOptions.sumLabel', {
|
||||
defaultMessage: 'Sum',
|
||||
}),
|
||||
value: 'sum',
|
||||
},
|
||||
];
|
||||
case ES_TYPES.KEYWORD:
|
||||
case ES_TYPES.STRING:
|
||||
return [
|
||||
{
|
||||
label: i18n.translate('tsvb.topHit.aggWithOptions.concatenate', {
|
||||
defaultMessage: 'Concatenate',
|
||||
}),
|
||||
value: 'concat',
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const getOrderOptions = () => [
|
||||
{
|
||||
label: i18n.translate('tsvb.topHit.orderOptions.ascLabel', {
|
||||
defaultMessage: 'Asc',
|
||||
}),
|
||||
value: 'asc',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.topHit.orderOptions.descLabel', {
|
||||
defaultMessage: 'Desc',
|
||||
}),
|
||||
value: 'desc',
|
||||
},
|
||||
];
|
||||
|
||||
const ORDER_DATE_RESTRICT_FIELDS = [ES_TYPES.DATE];
|
||||
|
||||
const TopHitAggUi = props => {
|
||||
const { fields, series, panel, intl } = props;
|
||||
const { fields, series, panel } = props;
|
||||
const defaults = {
|
||||
agg_with: 'avg',
|
||||
size: 1,
|
||||
agg_with: 'noop',
|
||||
order: 'desc',
|
||||
};
|
||||
const model = { ...defaults, ...props.model };
|
||||
|
@ -47,47 +116,30 @@ const TopHitAggUi = props => {
|
|||
(series.override_index_pattern && series.series_index_pattern) ||
|
||||
panel.index_pattern;
|
||||
|
||||
const aggWithOptionsRestrictFields = [
|
||||
PANEL_TYPES.TABLE,
|
||||
PANEL_TYPES.METRIC,
|
||||
PANEL_TYPES.MARKDOWN
|
||||
].includes(panel.type) ? [ES_TYPES.NUMBER, ES_TYPES.KEYWORD, ES_TYPES.STRING] : [ES_TYPES.NUMBER];
|
||||
|
||||
const handleChange = createChangeHandler(props.onChange, model);
|
||||
const handleSelectChange = createSelectHandler(handleChange);
|
||||
const handleTextChange = createTextHandler(handleChange);
|
||||
|
||||
const aggWithOptions = [
|
||||
{
|
||||
label: intl.formatMessage({ id: 'tsvb.topHit.aggWithOptions.averageLabel', defaultMessage: 'Avg' }),
|
||||
value: 'avg',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({ id: 'tsvb.topHit.aggWithOptions.maxLabel', defaultMessage: 'Max' }),
|
||||
value: 'max'
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({ id: 'tsvb.topHit.aggWithOptions.minLabel', defaultMessage: 'Min' }),
|
||||
value: 'min'
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({ id: 'tsvb.topHit.aggWithOptions.sumLabel', defaultMessage: 'Sum' }),
|
||||
value: 'sum'
|
||||
},
|
||||
];
|
||||
|
||||
const orderOptions = [
|
||||
{
|
||||
label: intl.formatMessage({ id: 'tsvb.topHit.orderOptions.ascLabel', defaultMessage: 'Asc' }),
|
||||
value: 'asc'
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({ id: 'tsvb.topHit.orderOptions.descLabel', defaultMessage: 'Desc' }),
|
||||
value: 'desc'
|
||||
},
|
||||
];
|
||||
const field = fields[indexPattern].find(f => f.name === model.field);
|
||||
const aggWithOptions = getAggWithOptions(field, aggWithOptionsRestrictFields);
|
||||
const orderOptions = getOrderOptions();
|
||||
|
||||
const htmlId = htmlIdGenerator();
|
||||
|
||||
const selectedAggWithOption = aggWithOptions.find(option => {
|
||||
return model.agg_with === option.value;
|
||||
});
|
||||
|
||||
const selectedOrderOption = orderOptions.find(option => {
|
||||
return model.order === option.value;
|
||||
});
|
||||
|
||||
return (
|
||||
<AggRow
|
||||
disableDelete={props.disableDelete}
|
||||
|
@ -123,7 +175,7 @@ const TopHitAggUi = props => {
|
|||
<FieldSelect
|
||||
fields={fields}
|
||||
type={model.type}
|
||||
restrict="numeric"
|
||||
restrict={aggWithOptionsRestrictFields}
|
||||
indexPattern={indexPattern}
|
||||
value={model.field}
|
||||
onChange={handleSelectChange('field')}
|
||||
|
@ -132,7 +184,7 @@ const TopHitAggUi = props => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiSpacer size="m"/>
|
||||
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -164,7 +216,9 @@ const TopHitAggUi = props => {
|
|||
>
|
||||
<EuiComboBox
|
||||
isClearable={false}
|
||||
placeholder={intl.formatMessage({ id: 'tsvb.topHit.aggregateWith.selectPlaceholder', defaultMessage: 'Select…' })}
|
||||
placeholder={i18n.translate('tsvb.topHit.aggregateWith.selectPlaceholder', {
|
||||
defaultMessage: 'Select...',
|
||||
})}
|
||||
options={aggWithOptions}
|
||||
selectedOptions={selectedAggWithOption ? [selectedAggWithOption] : []}
|
||||
onChange={handleSelectChange('agg_with')}
|
||||
|
@ -181,7 +235,7 @@ const TopHitAggUi = props => {
|
|||
/>)}
|
||||
>
|
||||
<FieldSelect
|
||||
restrict="date"
|
||||
restrict={ORDER_DATE_RESTRICT_FIELDS}
|
||||
value={model.order_by}
|
||||
onChange={handleSelectChange('order_by')}
|
||||
indexPattern={indexPattern}
|
||||
|
@ -199,7 +253,9 @@ const TopHitAggUi = props => {
|
|||
>
|
||||
<EuiComboBox
|
||||
isClearable={false}
|
||||
placeholder={intl.formatMessage({ id: 'tsvb.topHit.order.selectPlaceholder', defaultMessage: 'Select…' })}
|
||||
placeholder={i18n.translate('tsvb.topHit.order.selectPlaceholder', {
|
||||
defaultMessage: 'Select...',
|
||||
})}
|
||||
options={orderOptions}
|
||||
selectedOptions={selectedOrderOption ? [selectedOrderOption] : []}
|
||||
onChange={handleSelectChange('order')}
|
||||
|
|
|
@ -21,6 +21,7 @@ import PropTypes from 'prop-types';
|
|||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import * as collectionActions from './lib/collection_actions';
|
||||
import { ES_TYPES } from '../../common/es_types';
|
||||
import AddDeleteButtons from './add_delete_buttons';
|
||||
import ColorPicker from './color_picker';
|
||||
import FieldSelect from './aggs/field_select';
|
||||
|
@ -55,6 +56,8 @@ function newAnnotation() {
|
|||
};
|
||||
}
|
||||
|
||||
const RESTRICT_FIELDS = [ES_TYPES.DATE];
|
||||
|
||||
class AnnotationsEditor extends Component {
|
||||
|
||||
constructor(props) {
|
||||
|
@ -128,7 +131,7 @@ class AnnotationsEditor extends Component {
|
|||
fullWidth
|
||||
>
|
||||
<FieldSelect
|
||||
restrict="date"
|
||||
restrict={RESTRICT_FIELDS}
|
||||
value={model.time_field}
|
||||
onChange={this.handleChange(model, 'time_field')}
|
||||
indexPattern={model.index_pattern}
|
||||
|
|
|
@ -33,6 +33,9 @@ import {
|
|||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ES_TYPES } from '../../common/es_types';
|
||||
|
||||
const RESTRICT_FIELDS = [ES_TYPES.DATE];
|
||||
|
||||
export const IndexPattern = props => {
|
||||
const { fields, prefix } = props;
|
||||
|
@ -90,7 +93,7 @@ export const IndexPattern = props => {
|
|||
>
|
||||
<FieldSelect
|
||||
data-test-subj="metricsIndexPatternFieldsSelect"
|
||||
restrict="date"
|
||||
restrict={RESTRICT_FIELDS}
|
||||
value={model[timeFieldName]}
|
||||
disabled={props.disabled}
|
||||
onChange={handleSelectChange(timeFieldName)}
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* 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 generateByTypeFilter from '../generate_by_type_filter';
|
||||
import { expect } from 'chai';
|
||||
|
||||
describe('generateByTypeFilter()', () => {
|
||||
|
||||
describe('numeric', () => {
|
||||
const fn = generateByTypeFilter('numeric');
|
||||
[
|
||||
'number'
|
||||
].forEach((type) => {
|
||||
it(`should return true for ${type}`, () => expect(fn({ type })).to.equal(true));
|
||||
});
|
||||
});
|
||||
|
||||
describe('string', () => {
|
||||
const fn = generateByTypeFilter('string');
|
||||
['string', 'keyword', 'text'].forEach((type) => {
|
||||
it(`should return true for ${type}`, () => expect(fn({ type })).to.equal(true));
|
||||
});
|
||||
});
|
||||
|
||||
describe('date', () => {
|
||||
const fn = generateByTypeFilter('date');
|
||||
['date'].forEach((type) => {
|
||||
it(`should return true for ${type}`, () => expect(fn({ type })).to.equal(true));
|
||||
});
|
||||
});
|
||||
|
||||
describe('all', () => {
|
||||
const fn = generateByTypeFilter('all');
|
||||
[
|
||||
'number',
|
||||
'string',
|
||||
'text',
|
||||
'keyword',
|
||||
'date',
|
||||
'whatever'
|
||||
].forEach((type) => {
|
||||
it(`should return true for ${type}`, () => expect(fn({ type })).to.equal(true));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -61,11 +61,6 @@ describe('tickFormatter(format, template)', () => {
|
|||
expect(fn(1500)).to.equal('1.5k/s');
|
||||
});
|
||||
|
||||
it('returns zero if passed a string', () => {
|
||||
const fn = tickFormatter();
|
||||
expect(fn('100')).to.equal('0');
|
||||
});
|
||||
|
||||
it('returns value if passed a bad formatter', () => {
|
||||
const fn = tickFormatter('102');
|
||||
expect(fn(100)).to.equal('100');
|
||||
|
@ -78,6 +73,4 @@ describe('tickFormatter(format, template)', () => {
|
|||
const fn = tickFormatter('number', '{{value', (key) => config[key]);
|
||||
expect(fn(1.5556)).to.equal('1.56');
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
|
|
@ -55,7 +55,7 @@ export default (format = '0,0.[00]', template, getConfig = null) => {
|
|||
return (val) => {
|
||||
let value;
|
||||
if (!isNumber(val)) {
|
||||
value = 0;
|
||||
value = val;
|
||||
} else {
|
||||
try {
|
||||
value = formatter.convert(val, 'text');
|
||||
|
|
|
@ -23,6 +23,7 @@ import { keyCodes, EuiFlexGroup, EuiFlexItem, EuiButton, EuiText, EuiSwitch } fr
|
|||
import { getVisualizeLoader } from 'ui/visualize/loader/visualize_loader';
|
||||
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
|
||||
import { getInterval, convertIntervalIntoUnit, isIntervalValid, isGteInterval } from './lib/get_interval';
|
||||
import { PANEL_TYPES } from '../../common/panel_types';
|
||||
|
||||
const MIN_CHART_HEIGHT = 250;
|
||||
|
||||
|
@ -117,11 +118,11 @@ class VisEditorVisualization extends Component {
|
|||
const type = get(this.props, 'model.type', '');
|
||||
|
||||
return [
|
||||
'metric',
|
||||
'top_n',
|
||||
'gauge',
|
||||
'markdown',
|
||||
'table',
|
||||
PANEL_TYPES.METRIC,
|
||||
PANEL_TYPES.TOP_N,
|
||||
PANEL_TYPES.GAUGE,
|
||||
PANEL_TYPES.MARKDOWN,
|
||||
PANEL_TYPES.TABLE,
|
||||
].includes(type);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import PropTypes from 'prop-types';
|
|||
import React from 'react';
|
||||
import { EuiTabs, EuiTab } from '@elastic/eui';
|
||||
import { injectI18n } from '@kbn/i18n/react';
|
||||
import { PANEL_TYPES } from '../../common/panel_types';
|
||||
|
||||
function VisPickerItem(props) {
|
||||
const { label, type, selected } = props;
|
||||
|
@ -52,12 +53,12 @@ const VisPicker = injectI18n(function (props) {
|
|||
|
||||
const { model, intl } = props;
|
||||
const tabs = [
|
||||
{ type: 'timeseries', label: intl.formatMessage({ id: 'tsvb.visPicker.timeSeriesLabel', defaultMessage: 'Time Series' }) },
|
||||
{ type: 'metric', label: intl.formatMessage({ id: 'tsvb.visPicker.metricLabel', defaultMessage: 'Metric' }) },
|
||||
{ type: 'top_n', label: intl.formatMessage({ id: 'tsvb.visPicker.topNLabel', defaultMessage: 'Top N' }) },
|
||||
{ type: 'gauge', label: intl.formatMessage({ id: 'tsvb.visPicker.gaugeLabel', defaultMessage: 'Gauge' }) },
|
||||
{ type: 'markdown', label: 'Markdown' },
|
||||
{ type: 'table', label: intl.formatMessage({ id: 'tsvb.visPicker.tableLabel', defaultMessage: 'Table' }) }
|
||||
{ type: PANEL_TYPES.TIMESERIES, label: intl.formatMessage({ id: 'tsvb.visPicker.timeSeriesLabel', defaultMessage: 'Time Series' }) },
|
||||
{ type: PANEL_TYPES.METRIC, label: intl.formatMessage({ id: 'tsvb.visPicker.metricLabel', defaultMessage: 'Metric' }) },
|
||||
{ type: PANEL_TYPES.TOP_N, label: intl.formatMessage({ id: 'tsvb.visPicker.topNLabel', defaultMessage: 'Top N' }) },
|
||||
{ type: PANEL_TYPES.GAUGE, label: intl.formatMessage({ id: 'tsvb.visPicker.gaugeLabel', defaultMessage: 'Gauge' }) },
|
||||
{ type: PANEL_TYPES.MARKDOWN, label: 'Markdown' },
|
||||
{ type: PANEL_TYPES.TABLE, label: intl.formatMessage({ id: 'tsvb.visPicker.tableLabel', defaultMessage: 'Table' }) }
|
||||
].map(item => {
|
||||
return (
|
||||
<VisPickerItem
|
||||
|
|
|
@ -22,6 +22,8 @@ import { ReactEditorControllerProvider } from './editor_controller';
|
|||
import { VisFactoryProvider } from 'ui/vis/vis_factory';
|
||||
import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message';
|
||||
|
||||
import { PANEL_TYPES } from '../../common/panel_types';
|
||||
|
||||
// register the provider with the visTypes registry so that other know it exists
|
||||
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
|
||||
VisTypesRegistryProvider.register(MetricsVisProvider);
|
||||
|
@ -41,7 +43,7 @@ export default function MetricsVisProvider(Private, i18n) {
|
|||
visConfig: {
|
||||
defaults: {
|
||||
id: '61ca57f0-469d-11e7-af02-69e470af7417',
|
||||
type: 'timeseries',
|
||||
type: PANEL_TYPES.TIMESERIES,
|
||||
series: [
|
||||
{
|
||||
id: '61ca57f1-469d-11e7-af02-69e470af7417',
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { get, includes, max, min, sum } from 'lodash';
|
||||
import { get, includes, max, min, sum, noop } from 'lodash';
|
||||
import { toPercentileNumber } from '../../../../common/to_percentile_number';
|
||||
import { EXTENDED_STATS_TYPES, METRIC_TYPES } from '../../../../common/metric_types';
|
||||
|
||||
|
@ -25,6 +25,8 @@ const aggFns = {
|
|||
max,
|
||||
min,
|
||||
sum,
|
||||
noop,
|
||||
concat: values => values.join(', '),
|
||||
avg: values => sum(values) / values.length,
|
||||
};
|
||||
|
||||
|
@ -53,12 +55,14 @@ export default (row, metric) => {
|
|||
row[metric.id].values[percentileRankKey]
|
||||
);
|
||||
case METRIC_TYPES.TOP_HIT:
|
||||
if (row[metric.id].doc_count === 0) return null;
|
||||
if (row[metric.id].doc_count === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hits = get(row, [metric.id, 'docs', 'hits', 'hits'], []);
|
||||
const values = hits.map(doc => {
|
||||
return get(doc, `_source.${metric.field}`, 0);
|
||||
});
|
||||
const aggWith = (metric.agg_with && aggFns[metric.agg_with]) || aggFns.avg;
|
||||
const values = hits.map(doc => get(doc, `_source.${metric.field}`));
|
||||
const aggWith = (metric.agg_with && aggFns[metric.agg_with]) || aggFns.noop;
|
||||
|
||||
return aggWith(values);
|
||||
case METRIC_TYPES.COUNT:
|
||||
return get(row, 'doc_count', null);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue