mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[TSVB] Allow custom label for fields via index pattern field management (#84612)
* [TSVB] Allow custom label for fields via index pattern field management Closes: #84336 * replace saveObject, elasticsearch client to new one * fix CI * update schema * fix Top Hit * some changes * partially move getting fields into client side * fix PR comments * fix issue with getting fields * move SanitizedFieldType to common types * fix issue on changing index pattern * fix issue * fix regression * some work * remove extractFieldName, createCustomLabelSelectHandler * request/response processors should be async * some work * remove tests for createCustomLabelSelectHandler * fix table * fix placeholder * some work * fix jest * fix CI * fix label for table view * test: visualize app visual builder switch index patterns should be able to switch between index patterns * fix functional tests * fix sorting * fix labels for entire timerange mode * add createFieldsFetcher method * table view - fix pivot label * fix PR comments * fix issue with selecting buckets scripts * fix types * Update create_select_handler.test.ts * fix PR comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
ef441cca24
commit
0b7e83f736
108 changed files with 980 additions and 699 deletions
|
@ -1,11 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`src/legacy/core_plugins/metrics/common/model_options.js MODEL_TYPES should match a snapshot of constants 1`] = `
|
||||
Object {
|
||||
"UNWEIGHTED": "simple",
|
||||
"WEIGHTED_EXPONENTIAL": "ewma",
|
||||
"WEIGHTED_EXPONENTIAL_DOUBLE": "holt",
|
||||
"WEIGHTED_EXPONENTIAL_TRIPLE": "holt_winters",
|
||||
"WEIGHTED_LINEAR": "linear",
|
||||
}
|
||||
`;
|
|
@ -18,14 +18,15 @@
|
|||
*/
|
||||
|
||||
import { isBasicAgg } from './agg_lookup';
|
||||
import { MetricsItemsSchema } from './types';
|
||||
|
||||
describe('aggLookup', () => {
|
||||
describe('isBasicAgg(metric)', () => {
|
||||
test('returns true for a basic metric (count)', () => {
|
||||
expect(isBasicAgg({ type: 'count' })).toEqual(true);
|
||||
expect(isBasicAgg({ type: 'count' } as MetricsItemsSchema)).toEqual(true);
|
||||
});
|
||||
test('returns false for a pipeline metric (derivative)', () => {
|
||||
expect(isBasicAgg({ type: 'derivative' })).toEqual(false);
|
||||
expect(isBasicAgg({ type: 'derivative' } as MetricsItemsSchema)).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -17,10 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { omit, pick, includes } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { MetricsItemsSchema } from './types';
|
||||
|
||||
export const lookup = {
|
||||
export const lookup: Record<string, string> = {
|
||||
count: i18n.translate('visTypeTimeseries.aggLookup.countLabel', { defaultMessage: 'Count' }),
|
||||
calculation: i18n.translate('visTypeTimeseries.aggLookup.calculationLabel', {
|
||||
defaultMessage: 'Calculation',
|
||||
|
@ -122,11 +123,11 @@ const pipeline = [
|
|||
|
||||
const byType = {
|
||||
_all: lookup,
|
||||
pipeline: pipeline,
|
||||
basic: _.omit(lookup, pipeline),
|
||||
metrics: _.pick(lookup, ['count', 'avg', 'min', 'max', 'sum', 'cardinality', 'value_count']),
|
||||
pipeline,
|
||||
basic: omit(lookup, pipeline),
|
||||
metrics: pick(lookup, ['count', 'avg', 'min', 'max', 'sum', 'cardinality', 'value_count']),
|
||||
};
|
||||
|
||||
export function isBasicAgg(item) {
|
||||
return _.includes(Object.keys(byType.basic), item.type);
|
||||
export function isBasicAgg(item: MetricsItemsSchema) {
|
||||
return includes(Object.keys(byType.basic), item.type);
|
||||
}
|
|
@ -18,66 +18,79 @@
|
|||
*/
|
||||
|
||||
import { calculateLabel } from './calculate_label';
|
||||
import type { MetricsItemsSchema } from './types';
|
||||
|
||||
describe('calculateLabel(metric, metrics)', () => {
|
||||
test('returns "Unknown" for empty metric', () => {
|
||||
expect(calculateLabel()).toEqual('Unknown');
|
||||
});
|
||||
|
||||
test('returns the metric.alias if set', () => {
|
||||
expect(calculateLabel({ alias: 'Example' })).toEqual('Example');
|
||||
expect(calculateLabel({ alias: 'Example' } as MetricsItemsSchema)).toEqual('Example');
|
||||
});
|
||||
|
||||
test('returns "Count" for a count metric', () => {
|
||||
expect(calculateLabel({ type: 'count' })).toEqual('Count');
|
||||
expect(calculateLabel({ type: 'count' } as MetricsItemsSchema)).toEqual('Count');
|
||||
});
|
||||
|
||||
test('returns "Calculation" for a bucket script metric', () => {
|
||||
expect(calculateLabel({ type: 'calculation' })).toEqual('Bucket Script');
|
||||
expect(calculateLabel({ type: 'calculation' } as MetricsItemsSchema)).toEqual('Bucket Script');
|
||||
});
|
||||
|
||||
test('returns formated label for series_agg', () => {
|
||||
const label = calculateLabel({ type: 'series_agg', function: 'max' });
|
||||
test('returns formatted label for series_agg', () => {
|
||||
const label = calculateLabel({ type: 'series_agg', function: 'max' } as MetricsItemsSchema);
|
||||
|
||||
expect(label).toEqual('Series Agg (max)');
|
||||
});
|
||||
|
||||
test('returns formated label for basic aggs', () => {
|
||||
const label = calculateLabel({ type: 'avg', field: 'memory' });
|
||||
test('returns formatted label for basic aggs', () => {
|
||||
const label = calculateLabel({ type: 'avg', field: 'memory' } as MetricsItemsSchema);
|
||||
|
||||
expect(label).toEqual('Average of memory');
|
||||
});
|
||||
|
||||
test('returns formated label for pipeline aggs', () => {
|
||||
const metric = { id: 2, type: 'derivative', field: 1 };
|
||||
const metrics = [{ id: 1, type: 'max', field: 'network.out.bytes' }, metric];
|
||||
test('returns formatted label for pipeline aggs', () => {
|
||||
const metric = ({ id: 2, type: 'derivative', field: 1 } as unknown) as MetricsItemsSchema;
|
||||
const metrics = ([
|
||||
{ id: 1, type: 'max', field: 'network.out.bytes' },
|
||||
metric,
|
||||
] as unknown) as MetricsItemsSchema[];
|
||||
const label = calculateLabel(metric, metrics);
|
||||
|
||||
expect(label).toEqual('Derivative of Max of network.out.bytes');
|
||||
});
|
||||
|
||||
test('returns formated label for derivative of percentile', () => {
|
||||
const metric = { id: 2, type: 'derivative', field: '1[50.0]' };
|
||||
const metrics = [{ id: 1, type: 'percentile', field: 'network.out.bytes' }, metric];
|
||||
test('returns formatted label for derivative of percentile', () => {
|
||||
const metric = ({
|
||||
id: 2,
|
||||
type: 'derivative',
|
||||
field: '1[50.0]',
|
||||
} as unknown) as MetricsItemsSchema;
|
||||
const metrics = ([
|
||||
{ id: 1, type: 'percentile', field: 'network.out.bytes' },
|
||||
metric,
|
||||
] as unknown) as MetricsItemsSchema[];
|
||||
const label = calculateLabel(metric, metrics);
|
||||
|
||||
expect(label).toEqual('Derivative of Percentile of network.out.bytes (50.0)');
|
||||
});
|
||||
|
||||
test('returns formated label for pipeline aggs (deep)', () => {
|
||||
const metric = { id: 3, type: 'derivative', field: 2 };
|
||||
const metrics = [
|
||||
test('returns formatted label for pipeline aggs (deep)', () => {
|
||||
const metric = ({ id: 3, type: 'derivative', field: 2 } as unknown) as MetricsItemsSchema;
|
||||
const metrics = ([
|
||||
{ id: 1, type: 'max', field: 'network.out.bytes' },
|
||||
{ id: 2, type: 'moving_average', field: 1 },
|
||||
metric,
|
||||
];
|
||||
] as unknown) as MetricsItemsSchema[];
|
||||
const label = calculateLabel(metric, metrics);
|
||||
|
||||
expect(label).toEqual('Derivative of Moving Average of Max of network.out.bytes');
|
||||
});
|
||||
|
||||
test('returns formated label for pipeline aggs uses alias for field metric', () => {
|
||||
const metric = { id: 2, type: 'derivative', field: 1 };
|
||||
const metrics = [
|
||||
test('returns formatted label for pipeline aggs uses alias for field metric', () => {
|
||||
const metric = ({ id: 2, type: 'derivative', field: 1 } as unknown) as MetricsItemsSchema;
|
||||
const metrics = ([
|
||||
{ id: 1, type: 'max', field: 'network.out.bytes', alias: 'Outbound Traffic' },
|
||||
metric,
|
||||
];
|
||||
] as unknown) as MetricsItemsSchema[];
|
||||
const label = calculateLabel(metric, metrics);
|
||||
|
||||
expect(label).toEqual('Derivative of Outbound Traffic');
|
||||
});
|
||||
});
|
|
@ -18,8 +18,9 @@
|
|||
*/
|
||||
|
||||
import { includes, startsWith } from 'lodash';
|
||||
import { lookup } from './agg_lookup';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { lookup } from './agg_lookup';
|
||||
import { MetricsItemsSchema, SanitizedFieldType } from './types';
|
||||
|
||||
const paths = [
|
||||
'cumulative_sum',
|
||||
|
@ -36,7 +37,15 @@ const paths = [
|
|||
'positive_only',
|
||||
];
|
||||
|
||||
export function calculateLabel(metric, metrics) {
|
||||
export const extractFieldLabel = (fields: SanitizedFieldType[], name: string) => {
|
||||
return fields.find((f) => f.name === name)?.label ?? name;
|
||||
};
|
||||
|
||||
export const calculateLabel = (
|
||||
metric: MetricsItemsSchema,
|
||||
metrics: MetricsItemsSchema[] = [],
|
||||
fields: SanitizedFieldType[] = []
|
||||
): string => {
|
||||
if (!metric) {
|
||||
return i18n.translate('visTypeTimeseries.calculateLabel.unknownLabel', {
|
||||
defaultMessage: 'Unknown',
|
||||
|
@ -73,7 +82,7 @@ export function calculateLabel(metric, metrics) {
|
|||
if (metric.type === 'positive_rate') {
|
||||
return i18n.translate('visTypeTimeseries.calculateLabel.positiveRateLabel', {
|
||||
defaultMessage: 'Counter Rate of {field}',
|
||||
values: { field: metric.field },
|
||||
values: { field: extractFieldLabel(fields, metric.field!) },
|
||||
});
|
||||
}
|
||||
if (metric.type === 'static') {
|
||||
|
@ -84,15 +93,15 @@ export function calculateLabel(metric, metrics) {
|
|||
}
|
||||
|
||||
if (includes(paths, metric.type)) {
|
||||
const targetMetric = metrics.find((m) => startsWith(metric.field, m.id));
|
||||
const targetLabel = calculateLabel(targetMetric, metrics);
|
||||
const targetMetric = metrics.find((m) => startsWith(metric.field!, m.id));
|
||||
const targetLabel = calculateLabel(targetMetric!, metrics, fields);
|
||||
|
||||
// For percentiles we need to parse the field id to extract the percentile
|
||||
// the user configured in the percentile aggregation and specified in the
|
||||
// submetric they selected. This applies only to pipeline aggs.
|
||||
if (targetMetric && targetMetric.type === 'percentile') {
|
||||
const percentileValueMatch = /\[([0-9\.]+)\]$/;
|
||||
const matches = metric.field.match(percentileValueMatch);
|
||||
const matches = metric.field!.match(percentileValueMatch);
|
||||
if (matches) {
|
||||
return i18n.translate(
|
||||
'visTypeTimeseries.calculateLabel.lookupMetricTypeOfTargetWithAdditionalLabel',
|
||||
|
@ -115,6 +124,9 @@ export function calculateLabel(metric, metrics) {
|
|||
|
||||
return i18n.translate('visTypeTimeseries.calculateLabel.lookupMetricTypeOfMetricFieldRankLabel', {
|
||||
defaultMessage: '{lookupMetricType} of {metricField}',
|
||||
values: { lookupMetricType: lookup[metric.type], metricField: metric.field },
|
||||
values: {
|
||||
lookupMetricType: lookup[metric.type],
|
||||
metricField: extractFieldLabel(fields, metric.field!),
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
|
@ -18,16 +18,13 @@
|
|||
*/
|
||||
|
||||
import { extractIndexPatterns } from './extract_index_patterns';
|
||||
import { PanelSchema } from './types';
|
||||
|
||||
describe('extractIndexPatterns(vis)', () => {
|
||||
let visParams;
|
||||
let visFields;
|
||||
let panel: PanelSchema;
|
||||
|
||||
beforeEach(() => {
|
||||
visFields = {
|
||||
'*': [],
|
||||
};
|
||||
visParams = {
|
||||
panel = {
|
||||
index_pattern: '*',
|
||||
series: [
|
||||
{
|
||||
|
@ -40,25 +37,10 @@ describe('extractIndexPatterns(vis)', () => {
|
|||
},
|
||||
],
|
||||
annotations: [{ index_pattern: 'notes-*' }, { index_pattern: 'example-1-*' }],
|
||||
};
|
||||
} as PanelSchema;
|
||||
});
|
||||
|
||||
test('should return index patterns', () => {
|
||||
visFields = {};
|
||||
|
||||
expect(extractIndexPatterns(visParams, visFields)).toEqual([
|
||||
'*',
|
||||
'example-1-*',
|
||||
'example-2-*',
|
||||
'notes-*',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return index patterns that do not exist in visFields', () => {
|
||||
expect(extractIndexPatterns(visParams, visFields)).toEqual([
|
||||
'example-1-*',
|
||||
'example-2-*',
|
||||
'notes-*',
|
||||
]);
|
||||
expect(extractIndexPatterns(panel, '')).toEqual(['*', 'example-1-*', 'example-2-*', 'notes-*']);
|
||||
});
|
||||
});
|
|
@ -17,17 +17,21 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { uniq } from 'lodash';
|
||||
import { PanelSchema } from '../common/types';
|
||||
|
||||
export function extractIndexPatterns(panel, excludedFields = {}) {
|
||||
const patterns = [];
|
||||
export function extractIndexPatterns(
|
||||
panel: PanelSchema,
|
||||
defaultIndex?: PanelSchema['default_index_pattern']
|
||||
) {
|
||||
const patterns: string[] = [];
|
||||
|
||||
if (!excludedFields[panel.index_pattern]) {
|
||||
if (panel.index_pattern) {
|
||||
patterns.push(panel.index_pattern);
|
||||
}
|
||||
|
||||
panel.series.forEach((series) => {
|
||||
const indexPattern = series.series_index_pattern;
|
||||
if (indexPattern && series.override_index_pattern && !excludedFields[indexPattern]) {
|
||||
if (indexPattern && series.override_index_pattern) {
|
||||
patterns.push(indexPattern);
|
||||
}
|
||||
});
|
||||
|
@ -35,15 +39,15 @@ export function extractIndexPatterns(panel, excludedFields = {}) {
|
|||
if (panel.annotations) {
|
||||
panel.annotations.forEach((item) => {
|
||||
const indexPattern = item.index_pattern;
|
||||
if (indexPattern && !excludedFields[indexPattern]) {
|
||||
if (indexPattern) {
|
||||
patterns.push(indexPattern);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (patterns.length === 0) {
|
||||
patterns.push('');
|
||||
if (patterns.length === 0 && defaultIndex) {
|
||||
patterns.push(defaultIndex);
|
||||
}
|
||||
|
||||
return uniq(patterns).sort();
|
||||
return uniq<string>(patterns).sort();
|
||||
}
|
|
@ -17,10 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export const FIELD_TYPES = {
|
||||
BOOLEAN: 'boolean',
|
||||
DATE: 'date',
|
||||
GEO: 'geo_point',
|
||||
NUMBER: 'number',
|
||||
STRING: 'string',
|
||||
};
|
||||
export enum FIELD_TYPES {
|
||||
BOOLEAN = 'boolean',
|
||||
DATE = 'date',
|
||||
GEO = 'geo_point',
|
||||
NUMBER = 'number',
|
||||
STRING = 'string',
|
||||
}
|
|
@ -17,10 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export const MODEL_TYPES = {
|
||||
UNWEIGHTED: 'simple',
|
||||
WEIGHTED_EXPONENTIAL: 'ewma',
|
||||
WEIGHTED_EXPONENTIAL_DOUBLE: 'holt',
|
||||
WEIGHTED_EXPONENTIAL_TRIPLE: 'holt_winters',
|
||||
WEIGHTED_LINEAR: 'linear',
|
||||
};
|
||||
export enum MODEL_TYPES {
|
||||
UNWEIGHTED = 'simple',
|
||||
WEIGHTED_EXPONENTIAL = 'ewma',
|
||||
WEIGHTED_EXPONENTIAL_DOUBLE = 'holt',
|
||||
WEIGHTED_EXPONENTIAL_TRIPLE = 'holt_winters',
|
||||
WEIGHTED_LINEAR = 'linear',
|
||||
}
|
|
@ -22,19 +22,19 @@
|
|||
* @constant
|
||||
* @public
|
||||
*/
|
||||
export const TIME_RANGE_DATA_MODES = {
|
||||
export enum TIME_RANGE_DATA_MODES {
|
||||
/**
|
||||
* Entire timerange mode will match all the documents selected in the
|
||||
* timerange timepicker
|
||||
*/
|
||||
ENTIRE_TIME_RANGE: 'entire_time_range',
|
||||
ENTIRE_TIME_RANGE = 'entire_time_range',
|
||||
|
||||
/**
|
||||
* Last value mode will match only the documents for the specified interval
|
||||
* from the end of the timerange.
|
||||
*/
|
||||
LAST_VALUE: 'last_value',
|
||||
};
|
||||
LAST_VALUE = 'last_value',
|
||||
}
|
||||
|
||||
/**
|
||||
* Key for getting the Time Range mode from the Panel configuration object.
|
|
@ -18,5 +18,5 @@
|
|||
*/
|
||||
const percentileNumberTest = /\d+\.\d+/;
|
||||
|
||||
export const toPercentileNumber = (value) =>
|
||||
export const toPercentileNumber = (value: string) =>
|
||||
percentileNumberTest.test(`${value}`) ? value : `${value}.0`;
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import { metricsItems, panel, seriesItems, visPayloadSchema } from './vis_schema';
|
||||
import { metricsItems, panel, seriesItems, visPayloadSchema, fieldObject } from './vis_schema';
|
||||
import { PANEL_TYPES } from './panel_types';
|
||||
import { TimeseriesUIRestrictions } from './ui_restrictions';
|
||||
|
||||
|
@ -26,6 +26,7 @@ export type SeriesItemsSchema = TypeOf<typeof seriesItems>;
|
|||
export type MetricsItemsSchema = TypeOf<typeof metricsItems>;
|
||||
export type PanelSchema = TypeOf<typeof panel>;
|
||||
export type VisPayload = TypeOf<typeof visPayloadSchema>;
|
||||
export type FieldObject = TypeOf<typeof fieldObject>;
|
||||
|
||||
interface PanelData {
|
||||
id: string;
|
||||
|
@ -53,3 +54,9 @@ export type TimeseriesVisData = SeriesData & {
|
|||
*/
|
||||
series?: unknown[];
|
||||
};
|
||||
|
||||
export interface SanitizedFieldType {
|
||||
name: string;
|
||||
type: string;
|
||||
label?: string;
|
||||
}
|
||||
|
|
|
@ -47,6 +47,8 @@ const numberOptionalOrEmptyString = schema.maybe(
|
|||
schema.oneOf([numberOptional, schema.literal('')])
|
||||
);
|
||||
|
||||
export const fieldObject = stringOptionalNullable;
|
||||
|
||||
const annotationsItems = schema.object({
|
||||
color: stringOptionalNullable,
|
||||
fields: stringOptionalNullable,
|
||||
|
@ -58,7 +60,7 @@ const annotationsItems = schema.object({
|
|||
index_pattern: stringOptionalNullable,
|
||||
query_string: schema.maybe(queryObject),
|
||||
template: stringOptionalNullable,
|
||||
time_field: stringOptionalNullable,
|
||||
time_field: fieldObject,
|
||||
});
|
||||
|
||||
const backgroundColorRulesItems = schema.object({
|
||||
|
@ -77,8 +79,9 @@ const gaugeColorRulesItems = schema.object({
|
|||
value: schema.maybe(schema.nullable(schema.number())),
|
||||
});
|
||||
export const metricsItems = schema.object({
|
||||
field: stringOptionalNullable,
|
||||
field: fieldObject,
|
||||
id: stringRequired,
|
||||
alias: stringOptionalNullable,
|
||||
metric_agg: stringOptionalNullable,
|
||||
numerator: schema.maybe(queryObject),
|
||||
denominator: schema.maybe(queryObject),
|
||||
|
@ -98,7 +101,7 @@ export const metricsItems = schema.object({
|
|||
variables: schema.maybe(
|
||||
schema.arrayOf(
|
||||
schema.object({
|
||||
field: stringOptionalNullable,
|
||||
field: fieldObject,
|
||||
id: stringRequired,
|
||||
name: stringOptionalNullable,
|
||||
})
|
||||
|
@ -109,7 +112,7 @@ export const metricsItems = schema.object({
|
|||
schema.arrayOf(
|
||||
schema.object({
|
||||
id: stringRequired,
|
||||
field: stringOptionalNullable,
|
||||
field: fieldObject,
|
||||
mode: schema.oneOf([schema.literal('line'), schema.literal('band')]),
|
||||
shade: schema.oneOf([numberOptional, stringOptionalNullable]),
|
||||
value: schema.maybe(schema.oneOf([numberOptional, stringOptionalNullable])),
|
||||
|
@ -123,7 +126,7 @@ export const metricsItems = schema.object({
|
|||
size: stringOrNumberOptionalNullable,
|
||||
agg_with: stringOptionalNullable,
|
||||
order: stringOptionalNullable,
|
||||
order_by: stringOptionalNullable,
|
||||
order_by: fieldObject,
|
||||
});
|
||||
|
||||
const splitFiltersItems = schema.object({
|
||||
|
@ -134,7 +137,7 @@ const splitFiltersItems = schema.object({
|
|||
});
|
||||
|
||||
export const seriesItems = schema.object({
|
||||
aggregate_by: stringOptionalNullable,
|
||||
aggregate_by: fieldObject,
|
||||
aggregate_function: stringOptionalNullable,
|
||||
axis_position: stringRequired,
|
||||
axis_max: stringOrNumberOptionalNullable,
|
||||
|
@ -176,7 +179,7 @@ export const seriesItems = schema.object({
|
|||
seperate_axis: numberIntegerOptional,
|
||||
series_index_pattern: stringOptionalNullable,
|
||||
series_max_bars: numberIntegerOptional,
|
||||
series_time_field: stringOptionalNullable,
|
||||
series_time_field: fieldObject,
|
||||
series_interval: stringOptionalNullable,
|
||||
series_drop_last_bucket: numberIntegerOptional,
|
||||
split_color_mode: stringOptionalNullable,
|
||||
|
@ -184,7 +187,7 @@ export const seriesItems = schema.object({
|
|||
split_mode: stringRequired,
|
||||
stacked: stringRequired,
|
||||
steps: numberIntegerOptional,
|
||||
terms_field: stringOptionalNullable,
|
||||
terms_field: fieldObject,
|
||||
terms_order_by: stringOptionalNullable,
|
||||
terms_size: stringOptionalNullable,
|
||||
terms_direction: stringOptionalNullable,
|
||||
|
@ -241,7 +244,7 @@ export const panel = schema.object({
|
|||
markdown_vertical_align: stringOptionalNullable,
|
||||
markdown_less: stringOptionalNullable,
|
||||
markdown_css: stringOptionalNullable,
|
||||
pivot_id: stringOptionalNullable,
|
||||
pivot_id: fieldObject,
|
||||
pivot_label: stringOptionalNullable,
|
||||
pivot_type: stringOptionalNullable,
|
||||
pivot_rows: stringOptionalNullable,
|
||||
|
@ -251,7 +254,7 @@ export const panel = schema.object({
|
|||
tooltip_mode: schema.maybe(
|
||||
schema.oneOf([schema.literal('show_all'), schema.literal('show_focused')])
|
||||
),
|
||||
time_field: stringOptionalNullable,
|
||||
time_field: fieldObject,
|
||||
time_range_mode: stringOptionalNullable,
|
||||
type: schema.oneOf([
|
||||
schema.literal('table'),
|
||||
|
|
|
@ -59,6 +59,10 @@ export function Agg(props: AggProps) {
|
|||
...props.style,
|
||||
};
|
||||
|
||||
const indexPattern =
|
||||
(props.series.override_index_pattern && props.series.series_index_pattern) ||
|
||||
props.panel.index_pattern;
|
||||
|
||||
return (
|
||||
<div className={props.className} style={style}>
|
||||
<Component
|
||||
|
@ -71,6 +75,7 @@ export function Agg(props: AggProps) {
|
|||
panel={props.panel}
|
||||
series={props.series}
|
||||
siblings={props.siblings}
|
||||
indexPattern={indexPattern}
|
||||
uiRestrictions={props.uiRestrictions}
|
||||
dragHandleProps={props.dragHandleProps}
|
||||
/>
|
||||
|
|
|
@ -44,7 +44,7 @@ const checkModel = (model) => Array.isArray(model.variables) && model.script !==
|
|||
|
||||
export function CalculationAgg(props) {
|
||||
const htmlId = htmlIdGenerator();
|
||||
const { siblings, model } = props;
|
||||
const { siblings, model, indexPattern, fields } = props;
|
||||
|
||||
const handleChange = createChangeHandler(props.onChange, model);
|
||||
const handleSelectChange = createSelectHandler(handleChange);
|
||||
|
@ -97,6 +97,8 @@ export function CalculationAgg(props) {
|
|||
<CalculationVars
|
||||
id={htmlId('variables')}
|
||||
metrics={siblings}
|
||||
indexPattern={indexPattern}
|
||||
fields={fields}
|
||||
onChange={handleChange}
|
||||
name="variables"
|
||||
model={model}
|
||||
|
@ -140,6 +142,7 @@ export function CalculationAgg(props) {
|
|||
CalculationAgg.propTypes = {
|
||||
disableDelete: PropTypes.bool,
|
||||
fields: PropTypes.object,
|
||||
indexPattern: PropTypes.string,
|
||||
model: PropTypes.object,
|
||||
onAdd: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
|
|
|
@ -36,10 +36,11 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
export function CumulativeSumAgg(props) {
|
||||
const { model, siblings } = props;
|
||||
const { model, siblings, fields, indexPattern } = props;
|
||||
const htmlId = htmlIdGenerator();
|
||||
const handleChange = createChangeHandler(props.onChange, model);
|
||||
const handleSelectChange = createSelectHandler(handleChange);
|
||||
|
||||
return (
|
||||
<AggRow
|
||||
disableDelete={props.disableDelete}
|
||||
|
@ -80,6 +81,7 @@ export function CumulativeSumAgg(props) {
|
|||
onChange={handleSelectChange('field')}
|
||||
metrics={siblings}
|
||||
metric={model}
|
||||
fields={fields[indexPattern]}
|
||||
value={model.field}
|
||||
exclude={[METRIC_TYPES.TOP_HIT]}
|
||||
/>
|
||||
|
@ -93,6 +95,7 @@ export function CumulativeSumAgg(props) {
|
|||
CumulativeSumAgg.propTypes = {
|
||||
disableDelete: PropTypes.bool,
|
||||
fields: PropTypes.object,
|
||||
indexPattern: PropTypes.string,
|
||||
model: PropTypes.object,
|
||||
onAdd: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
|
|
|
@ -38,7 +38,7 @@ import {
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
export const DerivativeAgg = (props) => {
|
||||
const { siblings } = props;
|
||||
const { siblings, fields, indexPattern } = props;
|
||||
|
||||
const defaults = { unit: '' };
|
||||
const model = { ...defaults, ...props.model };
|
||||
|
@ -91,6 +91,7 @@ export const DerivativeAgg = (props) => {
|
|||
onChange={handleSelectChange('field')}
|
||||
metrics={siblings}
|
||||
metric={model}
|
||||
fields={fields[indexPattern]}
|
||||
value={model.field}
|
||||
exclude={[METRIC_TYPES.TOP_HIT]}
|
||||
fullWidth
|
||||
|
@ -120,6 +121,7 @@ export const DerivativeAgg = (props) => {
|
|||
DerivativeAgg.propTypes = {
|
||||
disableDelete: PropTypes.bool,
|
||||
fields: PropTypes.object,
|
||||
indexPattern: PropTypes.string,
|
||||
model: PropTypes.object,
|
||||
onAdd: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
|
|
|
@ -1,115 +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 PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { EuiComboBox } from '@elastic/eui';
|
||||
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,
|
||||
fields,
|
||||
indexPattern,
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
restrict,
|
||||
placeholder,
|
||||
uiRestrictions,
|
||||
...rest
|
||||
}) {
|
||||
if (type === 'count') {
|
||||
return null;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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={placeholder}
|
||||
isDisabled={disabled}
|
||||
options={options}
|
||||
selectedOptions={selectedOptions}
|
||||
onChange={onChange}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
FieldSelectUi.defaultProps = {
|
||||
indexPattern: '',
|
||||
disabled: false,
|
||||
restrict: [],
|
||||
placeholder: i18n.translate('visTypeTimeseries.fieldSelect.selectFieldPlaceholder', {
|
||||
defaultMessage: 'Select field...',
|
||||
}),
|
||||
};
|
||||
|
||||
FieldSelectUi.propTypes = {
|
||||
disabled: PropTypes.bool,
|
||||
fields: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
indexPattern: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
restrict: PropTypes.array,
|
||||
type: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
uiRestrictions: PropTypes.object,
|
||||
placeholder: PropTypes.string,
|
||||
};
|
||||
|
||||
export const FieldSelect = injectI18n(FieldSelectUi);
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { EuiComboBox, EuiComboBoxProps, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
import { METRIC_TYPES } from '../../../../common/metric_types';
|
||||
|
||||
import type { SanitizedFieldType } from '../../../../common/types';
|
||||
import type { TimeseriesUIRestrictions } from '../../../../common/ui_restrictions';
|
||||
|
||||
// @ts-ignore
|
||||
import { isFieldEnabled } from '../../lib/check_ui_restrictions';
|
||||
|
||||
interface FieldSelectProps {
|
||||
type: string;
|
||||
fields: Record<string, SanitizedFieldType[]>;
|
||||
indexPattern: string;
|
||||
value: string;
|
||||
onChange: (options: Array<EuiComboBoxOptionOption<string>>) => void;
|
||||
disabled?: boolean;
|
||||
restrict?: string[];
|
||||
placeholder?: string;
|
||||
uiRestrictions?: TimeseriesUIRestrictions;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
const defaultPlaceholder = i18n.translate('visTypeTimeseries.fieldSelect.selectFieldPlaceholder', {
|
||||
defaultMessage: 'Select field...',
|
||||
});
|
||||
|
||||
const isFieldTypeEnabled = (fieldRestrictions: string[], fieldType: string) =>
|
||||
fieldRestrictions.length ? fieldRestrictions.includes(fieldType) : true;
|
||||
|
||||
const sortByLabel = (a: EuiComboBoxOptionOption<string>, b: EuiComboBoxOptionOption<string>) => {
|
||||
const getNormalizedString = (option: EuiComboBoxOptionOption<string>) =>
|
||||
(option.label || '').toLowerCase();
|
||||
|
||||
return getNormalizedString(a).localeCompare(getNormalizedString(b));
|
||||
};
|
||||
|
||||
export function FieldSelect({
|
||||
type,
|
||||
fields,
|
||||
indexPattern = '',
|
||||
value = '',
|
||||
onChange,
|
||||
disabled = false,
|
||||
restrict = [],
|
||||
placeholder = defaultPlaceholder,
|
||||
uiRestrictions,
|
||||
'data-test-subj': dataTestSubj = 'metricsIndexPatternFieldsSelect',
|
||||
}: FieldSelectProps) {
|
||||
if (type === METRIC_TYPES.COUNT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selectedOptions: Array<EuiComboBoxOptionOption<string>> = [];
|
||||
let newPlaceholder = placeholder;
|
||||
const groupedOptions: EuiComboBoxProps<string>['options'] = Object.values(
|
||||
(fields[indexPattern] || []).reduce<Record<string, EuiComboBoxOptionOption<string>>>(
|
||||
(acc, field) => {
|
||||
if (placeholder === field?.name) {
|
||||
newPlaceholder = field.label ?? field.name;
|
||||
}
|
||||
|
||||
if (
|
||||
isFieldTypeEnabled(restrict, field.type) &&
|
||||
isFieldEnabled(field.name, type, uiRestrictions)
|
||||
) {
|
||||
const item: EuiComboBoxOptionOption<string> = {
|
||||
value: field.name,
|
||||
label: field.label ?? field.name,
|
||||
};
|
||||
|
||||
const fieldTypeOptions = acc[field.type]?.options;
|
||||
|
||||
if (fieldTypeOptions) {
|
||||
fieldTypeOptions.push(item);
|
||||
} else {
|
||||
acc[field.type] = {
|
||||
options: [item],
|
||||
label: field.type,
|
||||
};
|
||||
}
|
||||
|
||||
if (value === item.value) {
|
||||
selectedOptions.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
)
|
||||
);
|
||||
|
||||
// sort groups
|
||||
groupedOptions.sort(sortByLabel);
|
||||
|
||||
// sort items
|
||||
groupedOptions.forEach((group) => {
|
||||
if (Array.isArray(group.options)) {
|
||||
group.options.sort(sortByLabel);
|
||||
}
|
||||
});
|
||||
|
||||
if (value && !selectedOptions.length) {
|
||||
onChange([]);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiComboBox
|
||||
data-test-subj={dataTestSubj}
|
||||
placeholder={newPlaceholder}
|
||||
isDisabled={disabled}
|
||||
options={groupedOptions}
|
||||
selectedOptions={selectedOptions}
|
||||
onChange={onChange}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -24,6 +24,7 @@ import { FieldSelect } from './field_select';
|
|||
import { AggRow } from './agg_row';
|
||||
import { createChangeHandler } from '../lib/create_change_handler';
|
||||
import { createSelectHandler } from '../lib/create_select_handler';
|
||||
|
||||
import {
|
||||
htmlIdGenerator,
|
||||
EuiFlexGroup,
|
||||
|
|
|
@ -42,7 +42,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
const checkModel = (model) => Array.isArray(model.variables) && model.script !== undefined;
|
||||
|
||||
export function MathAgg(props) {
|
||||
const { siblings, model } = props;
|
||||
const { siblings, model, fields, indexPattern } = props;
|
||||
const htmlId = htmlIdGenerator();
|
||||
|
||||
const handleChange = createChangeHandler(props.onChange, model);
|
||||
|
@ -95,6 +95,8 @@ export function MathAgg(props) {
|
|||
<CalculationVars
|
||||
id={htmlId('variables')}
|
||||
metrics={siblings}
|
||||
fields={fields}
|
||||
indexPattern={indexPattern}
|
||||
onChange={handleChange}
|
||||
name="variables"
|
||||
model={model}
|
||||
|
@ -159,6 +161,7 @@ export function MathAgg(props) {
|
|||
MathAgg.propTypes = {
|
||||
disableDelete: PropTypes.bool,
|
||||
fields: PropTypes.object,
|
||||
indexPattern: PropTypes.string,
|
||||
model: PropTypes.object,
|
||||
onAdd: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
|
|
|
@ -19,21 +19,23 @@
|
|||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { includes } from 'lodash';
|
||||
import { injectI18n } from '@kbn/i18n/react';
|
||||
import { EuiComboBox } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { calculateSiblings } from '../lib/calculate_siblings';
|
||||
import { calculateLabel } from '../../../../common/calculate_label';
|
||||
import { basicAggs } from '../../../../common/basic_aggs';
|
||||
import { toPercentileNumber } from '../../../../common/to_percentile_number';
|
||||
import { METRIC_TYPES } from '../../../../common/metric_types';
|
||||
|
||||
function createTypeFilter(restrict, exclude) {
|
||||
function createTypeFilter(restrict, exclude = []) {
|
||||
return (metric) => {
|
||||
if (includes(exclude, metric.type)) return false;
|
||||
if (exclude.includes(metric.type)) {
|
||||
return false;
|
||||
}
|
||||
switch (restrict) {
|
||||
case 'basic':
|
||||
return includes(basicAggs, metric.type);
|
||||
return basicAggs.includes(metric.type);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
@ -55,21 +57,20 @@ export function filterRows(includeSiblings) {
|
|||
};
|
||||
}
|
||||
|
||||
function MetricSelectUi(props) {
|
||||
export function MetricSelect(props) {
|
||||
const {
|
||||
additionalOptions,
|
||||
restrict,
|
||||
metric,
|
||||
fields,
|
||||
metrics,
|
||||
onChange,
|
||||
value,
|
||||
exclude,
|
||||
includeSiblings,
|
||||
clearable,
|
||||
intl,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const calculatedMetrics = metrics.filter(createTypeFilter(restrict, exclude));
|
||||
|
||||
const siblings = calculateSiblings(calculatedMetrics, metric);
|
||||
|
@ -80,7 +81,7 @@ function MetricSelectUi(props) {
|
|||
const percentileOptions = siblings
|
||||
.filter((row) => /^percentile/.test(row.type))
|
||||
.reduce((acc, row) => {
|
||||
const label = calculateLabel(row, calculatedMetrics);
|
||||
const label = calculateLabel(row, calculatedMetrics, fields);
|
||||
|
||||
switch (row.type) {
|
||||
case METRIC_TYPES.PERCENTILE_RANK:
|
||||
|
@ -110,7 +111,7 @@ function MetricSelectUi(props) {
|
|||
}, []);
|
||||
|
||||
const options = siblings.filter(filterRows(includeSiblings)).map((row) => {
|
||||
const label = calculateLabel(row, calculatedMetrics);
|
||||
const label = calculateLabel(row, calculatedMetrics, fields);
|
||||
return { value: row.id, label };
|
||||
});
|
||||
const allOptions = [...options, ...additionalOptions, ...percentileOptions];
|
||||
|
@ -122,8 +123,7 @@ function MetricSelectUi(props) {
|
|||
|
||||
return (
|
||||
<EuiComboBox
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'visTypeTimeseries.metricSelect.selectMetricPlaceholder',
|
||||
placeholder={i18n.translate('visTypeTimeseries.metricSelect.selectMetricPlaceholder', {
|
||||
defaultMessage: 'Select metric…',
|
||||
})}
|
||||
options={allOptions}
|
||||
|
@ -136,7 +136,7 @@ function MetricSelectUi(props) {
|
|||
);
|
||||
}
|
||||
|
||||
MetricSelectUi.defaultProps = {
|
||||
MetricSelect.defaultProps = {
|
||||
additionalOptions: [],
|
||||
exclude: [],
|
||||
metric: {},
|
||||
|
@ -144,7 +144,7 @@ MetricSelectUi.defaultProps = {
|
|||
includeSiblings: false,
|
||||
};
|
||||
|
||||
MetricSelectUi.propTypes = {
|
||||
MetricSelect.propTypes = {
|
||||
additionalOptions: PropTypes.array,
|
||||
exclude: PropTypes.array,
|
||||
metric: PropTypes.object,
|
||||
|
@ -153,5 +153,3 @@ MetricSelectUi.propTypes = {
|
|||
value: PropTypes.string,
|
||||
includeSiblings: PropTypes.bool,
|
||||
};
|
||||
|
||||
export const MetricSelect = injectI18n(MetricSelectUi);
|
||||
|
|
|
@ -53,7 +53,7 @@ const shouldShowHint = ({ model_type: type, window, period }) =>
|
|||
type === MODEL_TYPES.WEIGHTED_EXPONENTIAL_TRIPLE && period * 2 > window;
|
||||
|
||||
export const MovingAverageAgg = (props) => {
|
||||
const { siblings } = props;
|
||||
const { siblings, fields, indexPattern } = props;
|
||||
|
||||
const model = { ...DEFAULTS, ...props.model };
|
||||
const modelOptions = [
|
||||
|
@ -153,6 +153,7 @@ export const MovingAverageAgg = (props) => {
|
|||
onChange={handleSelectChange('field')}
|
||||
metrics={siblings}
|
||||
metric={model}
|
||||
fields={fields[indexPattern]}
|
||||
value={model.field}
|
||||
exclude={[METRIC_TYPES.TOP_HIT]}
|
||||
/>
|
||||
|
@ -315,6 +316,7 @@ export const MovingAverageAgg = (props) => {
|
|||
MovingAverageAgg.propTypes = {
|
||||
disableDelete: PropTypes.bool,
|
||||
fields: PropTypes.object,
|
||||
indexPattern: PropTypes.string,
|
||||
model: PropTypes.object,
|
||||
onAdd: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
|
|
|
@ -40,8 +40,8 @@ import { createNumberHandler } from '../../lib/create_number_handler';
|
|||
import { AggRow } from '../agg_row';
|
||||
import { PercentileRankValues } from './percentile_rank_values';
|
||||
|
||||
import { IFieldType, KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public';
|
||||
import { MetricsItemsSchema, PanelSchema, SeriesItemsSchema } from '../../../../../common/types';
|
||||
import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public';
|
||||
import { MetricsItemsSchema, PanelSchema, SanitizedFieldType } from '../../../../../common/types';
|
||||
import { DragHandleProps } from '../../../../types';
|
||||
import { PercentileHdr } from '../percentile_hdr';
|
||||
|
||||
|
@ -49,10 +49,10 @@ const RESTRICT_FIELDS = [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.HISTOGRAM];
|
|||
|
||||
interface PercentileRankAggProps {
|
||||
disableDelete: boolean;
|
||||
fields: IFieldType[];
|
||||
fields: Record<string, SanitizedFieldType[]>;
|
||||
indexPattern: string;
|
||||
model: MetricsItemsSchema;
|
||||
panel: PanelSchema;
|
||||
series: SeriesItemsSchema;
|
||||
siblings: MetricsItemsSchema[];
|
||||
dragHandleProps: DragHandleProps;
|
||||
onAdd(): void;
|
||||
|
@ -61,12 +61,10 @@ interface PercentileRankAggProps {
|
|||
}
|
||||
|
||||
export const PercentileRankAgg = (props: PercentileRankAggProps) => {
|
||||
const { series, panel, fields } = props;
|
||||
const { panel, fields, indexPattern } = props;
|
||||
const defaults = { values: [''] };
|
||||
const model = { ...defaults, ...props.model };
|
||||
|
||||
const indexPattern =
|
||||
(series.override_index_pattern && series.series_index_pattern) || panel.index_pattern;
|
||||
const htmlId = htmlIdGenerator();
|
||||
const isTablePanel = panel.type === 'table';
|
||||
const handleChange = createChangeHandler(props.onChange, model);
|
||||
|
@ -79,7 +77,6 @@ export const PercentileRankAgg = (props: PercentileRankAggProps) => {
|
|||
values,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<AggRow
|
||||
disableDelete={props.disableDelete}
|
||||
|
@ -121,7 +118,7 @@ export const PercentileRankAgg = (props: PercentileRankAggProps) => {
|
|||
type={model.type}
|
||||
restrict={RESTRICT_FIELDS}
|
||||
indexPattern={indexPattern}
|
||||
value={model.field}
|
||||
value={model.field ?? ''}
|
||||
onChange={handleSelectChange('field')}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
|
|
@ -36,7 +36,7 @@ import {
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
export const PositiveOnlyAgg = (props) => {
|
||||
const { siblings } = props;
|
||||
const { siblings, fields, indexPattern } = props;
|
||||
|
||||
const defaults = { unit: '' };
|
||||
const model = { ...defaults, ...props.model };
|
||||
|
@ -85,6 +85,7 @@ export const PositiveOnlyAgg = (props) => {
|
|||
onChange={handleSelectChange('field')}
|
||||
metrics={siblings}
|
||||
metric={model}
|
||||
fields={fields[indexPattern]}
|
||||
value={model.field}
|
||||
exclude={[METRIC_TYPES.TOP_HIT]}
|
||||
/>
|
||||
|
@ -98,6 +99,7 @@ export const PositiveOnlyAgg = (props) => {
|
|||
PositiveOnlyAgg.propTypes = {
|
||||
disableDelete: PropTypes.bool,
|
||||
fields: PropTypes.object,
|
||||
indexPattern: PropTypes.string,
|
||||
model: PropTypes.object,
|
||||
onAdd: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
|
|
|
@ -37,7 +37,7 @@ import {
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
export const SerialDiffAgg = (props) => {
|
||||
const { siblings } = props;
|
||||
const { siblings, fields, indexPattern } = props;
|
||||
const defaults = { lag: '' };
|
||||
const model = { ...defaults, ...props.model };
|
||||
|
||||
|
@ -87,6 +87,7 @@ export const SerialDiffAgg = (props) => {
|
|||
onChange={handleSelectChange('field')}
|
||||
metrics={siblings}
|
||||
metric={model}
|
||||
fields={fields[indexPattern]}
|
||||
value={model.field}
|
||||
exclude={[METRIC_TYPES.TOP_HIT]}
|
||||
/>
|
||||
|
@ -125,6 +126,7 @@ export const SerialDiffAgg = (props) => {
|
|||
SerialDiffAgg.propTypes = {
|
||||
disableDelete: PropTypes.bool,
|
||||
fields: PropTypes.object,
|
||||
indexPattern: PropTypes.string,
|
||||
model: PropTypes.object,
|
||||
onAdd: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
|
|
|
@ -37,8 +37,10 @@ import { getSupportedFieldsByMetricType } from '../lib/get_supported_fields_by_m
|
|||
|
||||
export function StandardAgg(props) {
|
||||
const { model, panel, series, fields, uiRestrictions } = props;
|
||||
|
||||
const handleChange = createChangeHandler(props.onChange, model);
|
||||
const handleSelectChange = createSelectHandler(handleChange);
|
||||
|
||||
const restrictFields = getSupportedFieldsByMetricType(model.type);
|
||||
const indexPattern =
|
||||
(series.override_index_pattern && series.series_index_pattern) || panel.index_pattern;
|
||||
|
|
|
@ -40,7 +40,7 @@ import {
|
|||
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
const StandardSiblingAggUi = (props) => {
|
||||
const { siblings, intl } = props;
|
||||
const { siblings, intl, fields, indexPattern } = props;
|
||||
const defaults = { sigma: '' };
|
||||
const model = { ...defaults, ...props.model };
|
||||
const htmlId = htmlIdGenerator();
|
||||
|
@ -158,6 +158,7 @@ const StandardSiblingAggUi = (props) => {
|
|||
onChange={handleSelectChange('field')}
|
||||
exclude={[METRIC_TYPES.PERCENTILE, METRIC_TYPES.TOP_HIT]}
|
||||
metrics={siblings}
|
||||
fields={fields[indexPattern]}
|
||||
metric={model}
|
||||
value={model.field}
|
||||
/>
|
||||
|
@ -173,6 +174,7 @@ const StandardSiblingAggUi = (props) => {
|
|||
StandardSiblingAggUi.propTypes = {
|
||||
disableDelete: PropTypes.bool,
|
||||
fields: PropTypes.object,
|
||||
indexPattern: PropTypes.string,
|
||||
model: PropTypes.object,
|
||||
onAdd: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
|
|
|
@ -70,6 +70,7 @@ export class CalculationVars extends Component {
|
|||
metrics={this.props.metrics}
|
||||
metric={this.props.model}
|
||||
value={row.field}
|
||||
fields={this.props.fields[this.props.indexPattern]}
|
||||
includeSiblings={this.props.includeSiblings}
|
||||
exclude={this.props.exclude}
|
||||
/>
|
||||
|
@ -105,6 +106,8 @@ CalculationVars.defaultProps = {
|
|||
};
|
||||
|
||||
CalculationVars.propTypes = {
|
||||
fields: PropTypes.object,
|
||||
indexPattern: PropTypes.string,
|
||||
metrics: PropTypes.array,
|
||||
model: PropTypes.object,
|
||||
name: PropTypes.string,
|
||||
|
|
|
@ -74,6 +74,7 @@ export class AnnotationsEditor extends Component {
|
|||
handleChange(_.assign({}, item, part));
|
||||
};
|
||||
}
|
||||
|
||||
handleQueryChange = (model, filter) => {
|
||||
const part = { query_string: filter };
|
||||
collectionActions.handleChange(this.props, {
|
||||
|
|
|
@ -78,10 +78,6 @@ export const IndexPattern = ({
|
|||
allowLevelofDetail,
|
||||
}) => {
|
||||
const config = getUISettings();
|
||||
|
||||
const handleSelectChange = createSelectHandler(onChange);
|
||||
const handleTextChange = createTextHandler(onChange);
|
||||
|
||||
const timeFieldName = `${prefix}time_field`;
|
||||
const indexPatternName = `${prefix}index_pattern`;
|
||||
const intervalName = `${prefix}interval`;
|
||||
|
@ -100,6 +96,9 @@ export const IndexPattern = ({
|
|||
[onChange, maxBarsName]
|
||||
);
|
||||
|
||||
const handleSelectChange = createSelectHandler(onChange);
|
||||
const handleTextChange = createTextHandler(onChange);
|
||||
|
||||
const timeRangeOptions = [
|
||||
{
|
||||
label: i18n.translate('visTypeTimeseries.indexPattern.timeRange.lastValue', {
|
||||
|
@ -119,7 +118,7 @@ export const IndexPattern = ({
|
|||
|
||||
const defaults = {
|
||||
default_index_pattern: '',
|
||||
[indexPatternName]: '*',
|
||||
[indexPatternName]: '',
|
||||
[intervalName]: AUTO_INTERVAL,
|
||||
[dropBucketName]: 1,
|
||||
[maxBarsName]: config.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
|
||||
|
@ -191,7 +190,7 @@ export const IndexPattern = ({
|
|||
data-test-subj="metricsIndexPatternInput"
|
||||
disabled={disabled}
|
||||
placeholder={model.default_index_pattern}
|
||||
onChange={handleTextChange(indexPatternName, '*')}
|
||||
onChange={handleTextChange(indexPatternName)}
|
||||
value={model[indexPatternName]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -204,7 +203,6 @@ export const IndexPattern = ({
|
|||
})}
|
||||
>
|
||||
<FieldSelect
|
||||
data-test-subj="metricsIndexPatternFieldsSelect"
|
||||
restrict={RESTRICT_FIELDS}
|
||||
value={model[timeFieldName]}
|
||||
disabled={disabled}
|
||||
|
|
|
@ -17,23 +17,26 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { createSelectHandler } from './create_select_handler';
|
||||
import { createSelectHandler, HandleChange } from './create_select_handler';
|
||||
|
||||
describe('createSelectHandler()', () => {
|
||||
let handleChange;
|
||||
let changeHandler;
|
||||
describe('createSelectHandler', () => {
|
||||
describe('createSelectHandler()', () => {
|
||||
let handleChange: HandleChange;
|
||||
let changeHandler: ReturnType<typeof createSelectHandler>;
|
||||
|
||||
beforeEach(() => {
|
||||
handleChange = jest.fn();
|
||||
changeHandler = createSelectHandler(handleChange);
|
||||
const fn = changeHandler('test');
|
||||
fn([{ value: 'foo' }]);
|
||||
});
|
||||
beforeEach(() => {
|
||||
handleChange = jest.fn();
|
||||
changeHandler = createSelectHandler(handleChange);
|
||||
});
|
||||
|
||||
test('calls handleChange() function with partial', () => {
|
||||
expect(handleChange.mock.calls.length).toEqual(1);
|
||||
expect(handleChange.mock.calls[0][0]).toEqual({
|
||||
test: 'foo',
|
||||
test('should calls handleChange() function with the correct data', () => {
|
||||
const fn = changeHandler('test');
|
||||
|
||||
fn([{ value: 'foo', label: 'foo' }]);
|
||||
|
||||
expect(handleChange).toHaveBeenCalledWith({
|
||||
test: 'foo',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -16,13 +16,13 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
|
||||
import { MODEL_TYPES } from './model_options';
|
||||
export type HandleChange = (partialModel: Record<string, any>) => void;
|
||||
|
||||
describe('src/legacy/core_plugins/metrics/common/model_options.js', () => {
|
||||
describe('MODEL_TYPES', () => {
|
||||
test('should match a snapshot of constants', () => {
|
||||
expect(MODEL_TYPES).toMatchSnapshot();
|
||||
});
|
||||
export const createSelectHandler = (handleChange: HandleChange) => (name: string) => (
|
||||
selected: EuiComboBoxOptionOption[] = []
|
||||
) =>
|
||||
handleChange?.({
|
||||
[name]: selected[0]?.value ?? null,
|
||||
});
|
||||
});
|
|
@ -17,7 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { newMetricAggFn } from './new_metric_agg_fn';
|
||||
import { isBasicAgg } from '../../../../common/agg_lookup';
|
||||
import { handleAdd, handleChange } from './collection_actions';
|
||||
|
@ -30,8 +29,10 @@ export const seriesChangeHandler = (props, items) => (doc) => {
|
|||
handleAdd.call(null, props, () => {
|
||||
const metric = newMetricAggFn();
|
||||
metric.type = doc.type;
|
||||
const incompatPipelines = ['calculation', 'series_agg'];
|
||||
if (!_.includes(incompatPipelines, doc.type)) metric.field = doc.id;
|
||||
|
||||
if (!['calculation', 'series_agg'].includes(doc.type)) {
|
||||
metric.field = doc.id;
|
||||
}
|
||||
return metric;
|
||||
});
|
||||
} else {
|
||||
|
|
|
@ -46,7 +46,7 @@ const lessC = less(window, { env: 'production' });
|
|||
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
|
||||
import { QueryBarWrapper } from '../query_bar_wrapper';
|
||||
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
|
||||
import { VisDataContext } from './../../contexts/vis_data_context';
|
||||
import { VisDataContext } from '../../contexts/vis_data_context';
|
||||
|
||||
class MarkdownPanelConfigUi extends Component {
|
||||
constructor(props) {
|
||||
|
|
|
@ -45,7 +45,7 @@ import {
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { QueryBarWrapper } from '../query_bar_wrapper';
|
||||
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
|
||||
import { VisDataContext } from './../../contexts/vis_data_context';
|
||||
import { VisDataContext } from '../../contexts/vis_data_context';
|
||||
import { BUCKET_TYPES } from '../../../../common/metric_types';
|
||||
export class TablePanelConfig extends Component {
|
||||
static contextType = VisDataContext;
|
||||
|
|
|
@ -43,7 +43,7 @@ exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js
|
|||
}
|
||||
labelType="label"
|
||||
>
|
||||
<InjectIntl(FieldSelectUi)
|
||||
<FieldSelect
|
||||
data-test-subj="groupByField"
|
||||
fields={
|
||||
Object {
|
||||
|
@ -156,7 +156,7 @@ exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js
|
|||
}
|
||||
labelType="label"
|
||||
>
|
||||
<InjectIntl(MetricSelectUi)
|
||||
<MetricSelect
|
||||
additionalOptions={
|
||||
Array [
|
||||
Object {
|
||||
|
@ -170,6 +170,23 @@ exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js
|
|||
]
|
||||
}
|
||||
clearable={false}
|
||||
exclude={Array []}
|
||||
fields={
|
||||
Array [
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"esTypes": Array [
|
||||
"keyword",
|
||||
],
|
||||
"name": "OriginCityName",
|
||||
"readFromDocValues": true,
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
]
|
||||
}
|
||||
includeSiblings={false}
|
||||
metric={Object {}}
|
||||
onChange={[Function]}
|
||||
restrict="basic"
|
||||
value="_count"
|
||||
|
|
|
@ -213,6 +213,7 @@ export const SplitByTermsUI = ({
|
|||
metrics={metrics}
|
||||
clearable={false}
|
||||
additionalOptions={[defaultCount, terms]}
|
||||
fields={fields[indexPattern]}
|
||||
onChange={handleSelectChange('terms_order_by')}
|
||||
restrict="basic"
|
||||
value={model.terms_order_by}
|
||||
|
|
|
@ -40,13 +40,8 @@ export class VisEditor extends Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.localStorage = new Storage(window.localStorage);
|
||||
this.state = {
|
||||
model: props.visParams,
|
||||
dirty: false,
|
||||
autoApply: true,
|
||||
visFields: props.visFields,
|
||||
extractedIndexPatterns: [''],
|
||||
};
|
||||
this.state = {};
|
||||
|
||||
this.visDataSubject = new Rx.BehaviorSubject(this.props.visData);
|
||||
this.visData$ = this.visDataSubject.asObservable().pipe(share());
|
||||
|
||||
|
@ -75,7 +70,10 @@ export class VisEditor extends Component {
|
|||
isDirty: false,
|
||||
});
|
||||
|
||||
const extractedIndexPatterns = extractIndexPatterns(this.state.model);
|
||||
const extractedIndexPatterns = extractIndexPatterns(
|
||||
this.state.model,
|
||||
this.state.model.default_index_pattern
|
||||
);
|
||||
if (!isEqual(this.state.extractedIndexPatterns, extractedIndexPatterns)) {
|
||||
this.abortableFetchFields(extractedIndexPatterns).then((visFields) => {
|
||||
this.setState({
|
||||
|
@ -191,6 +189,31 @@ export class VisEditor extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
const dataStart = getDataStart();
|
||||
|
||||
dataStart.indexPatterns.getDefault().then(async (index) => {
|
||||
const defaultIndexTitle = index?.title ?? '';
|
||||
const indexPatterns = extractIndexPatterns(this.props.visParams, defaultIndexTitle);
|
||||
|
||||
this.setState({
|
||||
model: {
|
||||
...this.props.visParams,
|
||||
/** @legacy
|
||||
* please use IndexPatterns service instead
|
||||
* **/
|
||||
default_index_pattern: defaultIndexTitle,
|
||||
/** @legacy
|
||||
* please use IndexPatterns service instead
|
||||
* **/
|
||||
default_timefield: index?.timeFieldName ?? '',
|
||||
},
|
||||
dirty: false,
|
||||
autoApply: true,
|
||||
visFields: await fetchFields(indexPatterns),
|
||||
extractedIndexPatterns: [''],
|
||||
});
|
||||
});
|
||||
|
||||
this.props.eventEmitter.on('updateEditor', this.updateModel);
|
||||
}
|
||||
|
||||
|
@ -207,10 +230,8 @@ VisEditor.defaultProps = {
|
|||
VisEditor.propTypes = {
|
||||
vis: PropTypes.object,
|
||||
visData: PropTypes.object,
|
||||
visFields: PropTypes.object,
|
||||
renderComplete: PropTypes.func,
|
||||
config: PropTypes.object,
|
||||
savedObj: PropTypes.object,
|
||||
timeRange: PropTypes.object,
|
||||
appState: PropTypes.object,
|
||||
};
|
||||
|
|
|
@ -22,7 +22,6 @@ import React, { Component } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { RedirectAppLinks } from '../../../../../../kibana_react/public';
|
||||
import { createTickFormatter } from '../../lib/tick_formatter';
|
||||
import { calculateLabel } from '../../../../../common/calculate_label';
|
||||
import { isSortable } from './is_sortable';
|
||||
import { EuiToolTip, EuiIcon } from '@elastic/eui';
|
||||
import { replaceVars } from '../../lib/replace_vars';
|
||||
|
@ -30,8 +29,6 @@ import { fieldFormats } from '../../../../../../../plugins/data/public';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { getFieldFormats, getCoreStart } from '../../../../services';
|
||||
|
||||
import { METRIC_TYPES } from '../../../../../common/metric_types';
|
||||
|
||||
function getColor(rules, colorKey, value) {
|
||||
let color;
|
||||
if (rules) {
|
||||
|
@ -109,30 +106,19 @@ class TableVis extends Component {
|
|||
};
|
||||
|
||||
renderHeader() {
|
||||
const { model, uiState, onUiState } = this.props;
|
||||
const { model, uiState, onUiState, visData } = this.props;
|
||||
const stateKey = `${model.type}.sort`;
|
||||
const sort = uiState.get(stateKey, {
|
||||
column: '_default_',
|
||||
order: 'asc',
|
||||
});
|
||||
|
||||
const calculateHeaderLabel = (metric, item) => {
|
||||
const defaultLabel = item.label || calculateLabel(metric, item.metrics);
|
||||
|
||||
switch (metric.type) {
|
||||
case METRIC_TYPES.PERCENTILE:
|
||||
return `${defaultLabel} (${last(metric.percentiles).value || 0})`;
|
||||
case METRIC_TYPES.PERCENTILE_RANK:
|
||||
return `${defaultLabel} (${last(metric.values) || 0})`;
|
||||
default:
|
||||
return defaultLabel;
|
||||
}
|
||||
};
|
||||
const calculateHeaderLabel = (metric, item) =>
|
||||
item.label || visData.series[0]?.series?.find((s) => item.id === s.id)?.label;
|
||||
|
||||
const columns = this.visibleSeries.map((item) => {
|
||||
const metric = last(item.metrics);
|
||||
const label = calculateHeaderLabel(metric, item);
|
||||
|
||||
const handleClick = () => {
|
||||
if (!isSortable(metric)) return;
|
||||
let order;
|
||||
|
@ -179,7 +165,7 @@ class TableVis extends Component {
|
|||
</th>
|
||||
);
|
||||
});
|
||||
const label = model.pivot_label || model.pivot_field || model.pivot_id;
|
||||
const label = visData.pivot_label || model.pivot_label || model.pivot_id;
|
||||
let sortIcon;
|
||||
if (sort.column === '_default_') {
|
||||
sortIcon = sort.order === 'asc' ? 'sortUp' : 'sortDown';
|
||||
|
|
|
@ -20,8 +20,7 @@
|
|||
import React from 'react';
|
||||
import { getDisplayName } from './lib/get_display_name';
|
||||
import { labelDateFormatter } from './lib/label_date_formatter';
|
||||
import { last, findIndex, first } from 'lodash';
|
||||
import { calculateLabel } from '../../../common/calculate_label';
|
||||
import { findIndex, first } from 'lodash';
|
||||
|
||||
export function visWithSplits(WrappedComponent) {
|
||||
function SplitVisComponent(props) {
|
||||
|
@ -35,8 +34,8 @@ export function visWithSplits(WrappedComponent) {
|
|||
const [seriesId, splitId] = series.id.split(':');
|
||||
const seriesModel = model.series.find((s) => s.id === seriesId);
|
||||
if (!seriesModel || !splitId) return acc;
|
||||
const metric = last(seriesModel.metrics);
|
||||
const label = calculateLabel(metric, seriesModel.metrics);
|
||||
|
||||
const label = series.splitByLabel;
|
||||
|
||||
if (!acc[splitId]) {
|
||||
acc[splitId] = {
|
||||
|
@ -102,6 +101,7 @@ export function visWithSplits(WrappedComponent) {
|
|||
|
||||
return <div className="tvbSplitVis">{rows}</div>;
|
||||
}
|
||||
|
||||
SplitVisComponent.displayName = `SplitVisComponent(${getDisplayName(WrappedComponent)})`;
|
||||
return SplitVisComponent;
|
||||
}
|
||||
|
|
|
@ -19,8 +19,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { fetchIndexPatternFields } from './lib/fetch_fields';
|
||||
import { getSavedObjectsClient, getUISettings, getI18n } from '../services';
|
||||
import { getUISettings, getI18n } from '../services';
|
||||
import { VisEditor } from './components/vis_editor_lazy';
|
||||
|
||||
export class EditorController {
|
||||
|
@ -31,42 +30,18 @@ export class EditorController {
|
|||
this.eventEmitter = eventEmitter;
|
||||
|
||||
this.state = {
|
||||
fields: [],
|
||||
vis: vis,
|
||||
isLoaded: false,
|
||||
};
|
||||
}
|
||||
|
||||
fetchDefaultIndexPattern = async () => {
|
||||
const indexPattern = await getSavedObjectsClient().client.get(
|
||||
'index-pattern',
|
||||
getUISettings().get('defaultIndex')
|
||||
);
|
||||
|
||||
return indexPattern.attributes;
|
||||
};
|
||||
|
||||
fetchDefaultParams = async () => {
|
||||
const { title, timeFieldName } = await this.fetchDefaultIndexPattern();
|
||||
|
||||
this.state.vis.params.default_index_pattern = title;
|
||||
this.state.vis.params.default_timefield = timeFieldName;
|
||||
this.state.fields = await fetchIndexPatternFields(this.state.vis);
|
||||
|
||||
this.state.isLoaded = true;
|
||||
};
|
||||
|
||||
async render(params) {
|
||||
const I18nContext = getI18n().Context;
|
||||
|
||||
!this.state.isLoaded && (await this.fetchDefaultParams());
|
||||
|
||||
render(
|
||||
<I18nContext>
|
||||
<VisEditor
|
||||
config={getUISettings()}
|
||||
vis={this.state.vis}
|
||||
visFields={this.state.fields}
|
||||
visParams={this.state.vis.params}
|
||||
timeRange={params.timeRange}
|
||||
renderComplete={() => {}}
|
||||
|
|
|
@ -17,31 +17,43 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { extractIndexPatterns } from '../../../common/extract_index_patterns';
|
||||
import { getCoreStart } from '../../services';
|
||||
import { getCoreStart, getDataStart } from '../../services';
|
||||
import { ROUTES } from '../../../common/constants';
|
||||
import { SanitizedFieldType } from '../../../common/types';
|
||||
|
||||
export async function fetchFields(
|
||||
indexes: string[] = [],
|
||||
signal?: AbortSignal
|
||||
): Promise<Record<string, SanitizedFieldType[]>> {
|
||||
const patterns = Array.isArray(indexes) ? indexes : [indexes];
|
||||
const coreStart = getCoreStart();
|
||||
const dataStart = getDataStart();
|
||||
|
||||
export async function fetchFields(indexPatterns = [], signal) {
|
||||
const patterns = Array.isArray(indexPatterns) ? indexPatterns : [indexPatterns];
|
||||
try {
|
||||
const defaultIndexPattern = await dataStart.indexPatterns.getDefault();
|
||||
const indexFields = await Promise.all(
|
||||
patterns.map((pattern) =>
|
||||
getCoreStart().http.get(ROUTES.FIELDS, {
|
||||
patterns.map(async (pattern) => {
|
||||
return coreStart.http.get(ROUTES.FIELDS, {
|
||||
query: {
|
||||
index: pattern,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
)
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return patterns.reduce(
|
||||
const fields: Record<string, SanitizedFieldType[]> = patterns.reduce(
|
||||
(cumulatedFields, currentPattern, index) => ({
|
||||
...cumulatedFields,
|
||||
[currentPattern]: indexFields[index],
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
if (defaultIndexPattern?.title && patterns.includes(defaultIndexPattern.title)) {
|
||||
fields[''] = fields[defaultIndexPattern.title];
|
||||
}
|
||||
return fields;
|
||||
} catch (error) {
|
||||
if (error.name !== 'AbortError') {
|
||||
getCoreStart().notifications.toasts.addDanger({
|
||||
|
@ -52,11 +64,5 @@ export async function fetchFields(indexPatterns = [], signal) {
|
|||
});
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export async function fetchIndexPatternFields({ params, fields = {} }) {
|
||||
const indexPatterns = extractIndexPatterns(params, fields);
|
||||
|
||||
return await fetchFields(indexPatterns);
|
||||
return {};
|
||||
}
|
|
@ -16,24 +16,31 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { uniqBy, get } from 'lodash';
|
||||
import { uniqBy } from 'lodash';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
import { KibanaRequest, RequestHandlerContext } from 'kibana/server';
|
||||
|
||||
import { Framework } from '../plugin';
|
||||
import {
|
||||
indexPatterns,
|
||||
IndexPatternFieldDescriptor,
|
||||
IndexPatternsFetcher,
|
||||
} from '../../../data/server';
|
||||
import { IndexPatternsFetcher } from '../../../data/server';
|
||||
import { ReqFacade } from './search_strategies/strategies/abstract_search_strategy';
|
||||
|
||||
export async function getFields(
|
||||
requestContext: RequestHandlerContext,
|
||||
request: KibanaRequest,
|
||||
framework: Framework,
|
||||
indexPattern: string
|
||||
indexPatternString: string
|
||||
) {
|
||||
const getIndexPatternsService = async () => {
|
||||
const [, { data }] = await framework.core.getStartServices();
|
||||
|
||||
return await data.indexPatterns.indexPatternsServiceFactory(
|
||||
requestContext.core.savedObjects.client,
|
||||
requestContext.core.elasticsearch.client.asCurrentUser
|
||||
);
|
||||
};
|
||||
|
||||
const indexPatternsService = await getIndexPatternsService();
|
||||
|
||||
// NOTE / TODO: This facade has been put in place to make migrating to the New Platform easier. It
|
||||
// removes the need to refactor many layers of dependencies on "req", and instead just augments the top
|
||||
// level object passed from here. The layers should be refactored fully at some point, but for now
|
||||
|
@ -44,7 +51,7 @@ export async function getFields(
|
|||
framework,
|
||||
payload: {},
|
||||
pre: {
|
||||
indexPatternsService: new IndexPatternsFetcher(
|
||||
indexPatternsFetcher: new IndexPatternsFetcher(
|
||||
requestContext.core.elasticsearch.client.asCurrentUser
|
||||
),
|
||||
},
|
||||
|
@ -58,19 +65,13 @@ export async function getFields(
|
|||
)
|
||||
.toPromise();
|
||||
},
|
||||
getIndexPatternsService: async () => indexPatternsService,
|
||||
};
|
||||
let indexPatternString = indexPattern;
|
||||
|
||||
if (!indexPatternString) {
|
||||
const [{ savedObjects, elasticsearch }, { data }] = await framework.core.getStartServices();
|
||||
const savedObjectsClient = savedObjects.getScopedClient(request);
|
||||
const clusterClient = elasticsearch.client.asScoped(request).asCurrentUser;
|
||||
const indexPatternsService = await data.indexPatterns.indexPatternsServiceFactory(
|
||||
savedObjectsClient,
|
||||
clusterClient
|
||||
);
|
||||
const defaultIndexPattern = await indexPatternsService.getDefault();
|
||||
indexPatternString = get(defaultIndexPattern, 'title', '');
|
||||
|
||||
indexPatternString = defaultIndexPattern?.title ?? '';
|
||||
}
|
||||
|
||||
const {
|
||||
|
@ -78,12 +79,10 @@ export async function getFields(
|
|||
capabilities,
|
||||
} = (await framework.searchStrategyRegistry.getViableStrategy(reqFacade, indexPatternString))!;
|
||||
|
||||
const fields = ((await searchStrategy.getFieldsForWildcard(
|
||||
const fields = await searchStrategy.getFieldsForWildcard(
|
||||
reqFacade,
|
||||
indexPatternString,
|
||||
capabilities
|
||||
)) as IndexPatternFieldDescriptor[]).filter(
|
||||
(field) => field.aggregatable && !indexPatterns.isNestedField(field)
|
||||
);
|
||||
|
||||
return uniqBy(fields, (field) => field.name);
|
||||
|
|
|
@ -71,6 +71,14 @@ export function getVisData(
|
|||
)
|
||||
.toPromise();
|
||||
},
|
||||
getIndexPatternsService: async () => {
|
||||
const [, { data }] = await framework.core.getStartServices();
|
||||
|
||||
return await data.indexPatterns.indexPatternsServiceFactory(
|
||||
requestContext.core.savedObjects.client,
|
||||
requestContext.core.elasticsearch.client.asCurrentUser
|
||||
);
|
||||
},
|
||||
};
|
||||
const promises = reqFacade.payload.panels.map(getPanelData(reqFacade));
|
||||
return Promise.all(promises).then((res) => {
|
||||
|
|
|
@ -24,7 +24,8 @@ import { DefaultSearchStrategy } from './strategies/default_search_strategy';
|
|||
import { extractIndexPatterns } from '../../../common/extract_index_patterns';
|
||||
|
||||
export type RequestFacade = any;
|
||||
export type Panel = any;
|
||||
|
||||
import { PanelSchema } from '../../../common/types';
|
||||
|
||||
export class SearchStrategyRegistry {
|
||||
private strategies: AbstractSearchStrategy[] = [];
|
||||
|
@ -53,8 +54,8 @@ export class SearchStrategyRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
async getViableStrategyForPanel(req: RequestFacade, panel: Panel) {
|
||||
const indexPattern = extractIndexPatterns(panel).join(',');
|
||||
async getViableStrategyForPanel(req: RequestFacade, panel: PanelSchema) {
|
||||
const indexPattern = extractIndexPatterns(panel, panel.default_index_pattern).join(',');
|
||||
|
||||
return this.getViableStrategy(req, indexPattern);
|
||||
}
|
||||
|
|
|
@ -26,14 +26,17 @@ describe('AbstractSearchStrategy', () => {
|
|||
let indexPattern;
|
||||
|
||||
beforeEach(() => {
|
||||
mockedFields = {};
|
||||
mockedFields = [];
|
||||
req = {
|
||||
payload: {},
|
||||
pre: {
|
||||
indexPatternsService: {
|
||||
indexPatternsFetcher: {
|
||||
getFieldsForWildcard: jest.fn().mockReturnValue(mockedFields),
|
||||
},
|
||||
},
|
||||
getIndexPatternsService: jest.fn(() => ({
|
||||
find: jest.fn(() => []),
|
||||
})),
|
||||
};
|
||||
|
||||
abstractSearchStrategy = new AbstractSearchStrategy();
|
||||
|
@ -48,9 +51,10 @@ describe('AbstractSearchStrategy', () => {
|
|||
test('should return fields for wildcard', async () => {
|
||||
const fields = await abstractSearchStrategy.getFieldsForWildcard(req, indexPattern);
|
||||
|
||||
expect(fields).toBe(mockedFields);
|
||||
expect(req.pre.indexPatternsService.getFieldsForWildcard).toHaveBeenCalledWith({
|
||||
expect(fields).toEqual(mockedFields);
|
||||
expect(req.pre.indexPatternsFetcher.getFieldsForWildcard).toHaveBeenCalledWith({
|
||||
pattern: indexPattern,
|
||||
metaFields: [],
|
||||
fieldCapsOptions: { allow_no_indices: true },
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,16 +17,19 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
import type {
|
||||
RequestHandlerContext,
|
||||
FakeRequest,
|
||||
IUiSettingsClient,
|
||||
SavedObjectsClientContract,
|
||||
} from 'kibana/server';
|
||||
|
||||
import { Framework } from '../../../plugin';
|
||||
import { IndexPatternsFetcher } from '../../../../../data/server';
|
||||
import { VisPayload } from '../../../../common/types';
|
||||
import type { Framework } from '../../../plugin';
|
||||
import type { IndexPatternsFetcher, IFieldType } from '../../../../../data/server';
|
||||
import type { VisPayload } from '../../../../common/types';
|
||||
import type { IndexPatternsService } from '../../../../../data/common';
|
||||
import { indexPatterns } from '../../../../../data/server';
|
||||
import { SanitizedFieldType } from '../../../../common/types';
|
||||
|
||||
/**
|
||||
* ReqFacade is a regular KibanaRequest object extended with additional service
|
||||
|
@ -39,13 +42,27 @@ export interface ReqFacade<T = unknown> extends FakeRequest {
|
|||
framework: Framework;
|
||||
payload: T;
|
||||
pre: {
|
||||
indexPatternsService?: IndexPatternsFetcher;
|
||||
indexPatternsFetcher?: IndexPatternsFetcher;
|
||||
};
|
||||
getUiSettingsService: () => IUiSettingsClient;
|
||||
getSavedObjectsClient: () => SavedObjectsClientContract;
|
||||
getEsShardTimeout: () => Promise<number>;
|
||||
getIndexPatternsService: () => Promise<IndexPatternsService>;
|
||||
}
|
||||
|
||||
const toSanitizedFieldType = (fields: IFieldType[]) => {
|
||||
return fields
|
||||
.filter((field) => field.aggregatable && !indexPatterns.isNestedField(field))
|
||||
.map(
|
||||
(field: IFieldType) =>
|
||||
({
|
||||
name: field.name,
|
||||
label: field.customLabel ?? field.name,
|
||||
type: field.type,
|
||||
} as SanitizedFieldType)
|
||||
);
|
||||
};
|
||||
|
||||
export abstract class AbstractSearchStrategy {
|
||||
async search(req: ReqFacade<VisPayload>, bodies: any[], indexType?: string) {
|
||||
const requests: any[] = [];
|
||||
|
@ -81,13 +98,27 @@ export abstract class AbstractSearchStrategy {
|
|||
async getFieldsForWildcard<TPayload = unknown>(
|
||||
req: ReqFacade<TPayload>,
|
||||
indexPattern: string,
|
||||
capabilities?: unknown
|
||||
capabilities?: unknown,
|
||||
options?: Partial<{
|
||||
type: string;
|
||||
rollupIndex: string;
|
||||
}>
|
||||
) {
|
||||
const { indexPatternsService } = req.pre;
|
||||
const { indexPatternsFetcher } = req.pre;
|
||||
const indexPatternsService = await req.getIndexPatternsService();
|
||||
const kibanaIndexPattern = (await indexPatternsService.find(indexPattern)).find(
|
||||
(index) => index.title === indexPattern
|
||||
);
|
||||
|
||||
return await indexPatternsService!.getFieldsForWildcard({
|
||||
pattern: indexPattern,
|
||||
fieldCapsOptions: { allow_no_indices: true },
|
||||
});
|
||||
return toSanitizedFieldType(
|
||||
kibanaIndexPattern
|
||||
? kibanaIndexPattern.fields.getAll()
|
||||
: await indexPatternsFetcher!.getFieldsForWildcard({
|
||||
pattern: indexPattern,
|
||||
fieldCapsOptions: { allow_no_indices: true },
|
||||
metaFields: [],
|
||||
...options,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,4 +30,12 @@ export class DefaultSearchStrategy extends AbstractSearchStrategy {
|
|||
capabilities: new DefaultSearchCapabilities(req),
|
||||
});
|
||||
}
|
||||
|
||||
async getFieldsForWildcard<TPayload = unknown>(
|
||||
req: ReqFacade<TPayload>,
|
||||
indexPattern: string,
|
||||
capabilities?: unknown
|
||||
) {
|
||||
return super.getFieldsForWildcard(req, indexPattern, capabilities);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@ import { processors } from '../request_processors/annotations';
|
|||
* ]
|
||||
* @returns {Object} doc - processed body
|
||||
*/
|
||||
export function buildAnnotationRequest(...args) {
|
||||
export async function buildAnnotationRequest(...args) {
|
||||
const processor = buildProcessorFunction(processors, ...args);
|
||||
const doc = processor({});
|
||||
const doc = await processor({});
|
||||
return doc;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
import { buildAnnotationRequest } from './build_request_body';
|
||||
import { getEsShardTimeout } from '../helpers/get_es_shard_timeout';
|
||||
import { getIndexPatternObject } from '../helpers/get_index_pattern';
|
||||
import { UI_SETTINGS } from '../../../../../data/common';
|
||||
|
||||
export async function getAnnotationRequestParams(
|
||||
req,
|
||||
|
@ -32,17 +31,14 @@ export async function getAnnotationRequestParams(
|
|||
const esShardTimeout = await getEsShardTimeout(req);
|
||||
const indexPattern = annotation.index_pattern;
|
||||
const { indexPatternObject, indexPatternString } = await getIndexPatternObject(req, indexPattern);
|
||||
const request = buildAnnotationRequest(
|
||||
const request = await buildAnnotationRequest(
|
||||
req,
|
||||
panel,
|
||||
annotation,
|
||||
esQueryConfig,
|
||||
indexPatternObject,
|
||||
capabilities,
|
||||
{
|
||||
maxBarsUiSettings: await uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS),
|
||||
barTargetUiSettings: await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
|
||||
}
|
||||
uiSettings
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
@ -45,11 +45,14 @@ export async function getSeriesData(req, panel) {
|
|||
);
|
||||
const data = await searchStrategy.search(req, searches);
|
||||
|
||||
const handleResponseBodyFn = handleResponseBody(panel);
|
||||
const handleResponseBodyFn = handleResponseBody(panel, req, searchStrategy, capabilities);
|
||||
|
||||
const series = data.map((resp) =>
|
||||
handleResponseBodyFn(resp.rawResponse ? resp.rawResponse : resp)
|
||||
const series = await Promise.all(
|
||||
data.map(
|
||||
async (resp) => await handleResponseBodyFn(resp.rawResponse ? resp.rawResponse : resp)
|
||||
)
|
||||
);
|
||||
|
||||
let annotations = null;
|
||||
|
||||
if (panel.annotations && panel.annotations.length) {
|
||||
|
|
|
@ -16,13 +16,15 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { buildRequestBody } from './table/build_request_body';
|
||||
import { handleErrorResponse } from './handle_error_response';
|
||||
import { get } from 'lodash';
|
||||
import { processBucket } from './table/process_bucket';
|
||||
import { getEsQueryConfig } from './helpers/get_es_query_uisettings';
|
||||
import { getIndexPatternObject } from './helpers/get_index_pattern';
|
||||
import { UI_SETTINGS } from '../../../../data/common';
|
||||
import { createFieldsFetcher } from './helpers/fields_fetcher';
|
||||
import { extractFieldLabel } from '../../../common/calculate_label';
|
||||
|
||||
export async function getTableData(req, panel) {
|
||||
const panelIndexPattern = panel.index_pattern;
|
||||
|
@ -33,18 +35,33 @@ export async function getTableData(req, panel) {
|
|||
} = await req.framework.searchStrategyRegistry.getViableStrategy(req, panelIndexPattern);
|
||||
const esQueryConfig = await getEsQueryConfig(req);
|
||||
const { indexPatternObject } = await getIndexPatternObject(req, panelIndexPattern);
|
||||
const extractFields = createFieldsFetcher(req, searchStrategy, capabilities);
|
||||
|
||||
const calculatePivotLabel = async () => {
|
||||
if (panel.pivot_id && indexPatternObject?.title) {
|
||||
const fields = await extractFields(indexPatternObject.title);
|
||||
|
||||
return extractFieldLabel(fields, panel.pivot_id);
|
||||
}
|
||||
return panel.pivot_id;
|
||||
};
|
||||
|
||||
const meta = {
|
||||
type: panel.type,
|
||||
pivot_label: panel.pivot_label || (await calculatePivotLabel()),
|
||||
uiRestrictions: capabilities.uiRestrictions,
|
||||
};
|
||||
|
||||
try {
|
||||
const uiSettings = req.getUiSettingsService();
|
||||
const body = buildRequestBody(req, panel, esQueryConfig, indexPatternObject, capabilities, {
|
||||
maxBarsUiSettings: await uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS),
|
||||
barTargetUiSettings: await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
|
||||
});
|
||||
const body = await buildRequestBody(
|
||||
req,
|
||||
panel,
|
||||
esQueryConfig,
|
||||
indexPatternObject,
|
||||
capabilities,
|
||||
uiSettings
|
||||
);
|
||||
|
||||
const [resp] = await searchStrategy.search(req, [
|
||||
{
|
||||
|
@ -59,9 +76,13 @@ export async function getTableData(req, panel) {
|
|||
[]
|
||||
);
|
||||
|
||||
const series = await Promise.all(
|
||||
buckets.map(processBucket(panel, req, searchStrategy, capabilities, extractFields))
|
||||
);
|
||||
|
||||
return {
|
||||
...meta,
|
||||
series: buckets.map(processBucket(panel)),
|
||||
series,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err.body || err.name === 'KQLSyntaxError') {
|
||||
|
|
|
@ -128,7 +128,8 @@ export const bucketTransform = {
|
|||
},
|
||||
};
|
||||
if (bucket.order_by) {
|
||||
set(body, 'aggs.docs.top_hits.sort', [{ [bucket.order_by]: { order: bucket.order } }]);
|
||||
const orderField = bucket.order_by;
|
||||
set(body, 'aggs.docs.top_hits.sort', [{ [orderField]: { order: bucket.order } }]);
|
||||
}
|
||||
return body;
|
||||
},
|
||||
|
|
|
@ -17,12 +17,24 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { AbstractSearchStrategy, DefaultSearchCapabilities, ReqFacade } from '../../..';
|
||||
|
||||
export const createSelectHandler = (handleChange) => {
|
||||
return (name) => (selectedOptions) => {
|
||||
return handleChange?.({
|
||||
[name]: _.get(selectedOptions, '[0].value', null),
|
||||
});
|
||||
export const createFieldsFetcher = (
|
||||
req: ReqFacade,
|
||||
searchStrategy: AbstractSearchStrategy,
|
||||
capabilities: DefaultSearchCapabilities
|
||||
) => {
|
||||
const fieldsCacheMap = new Map();
|
||||
|
||||
return async (index: string) => {
|
||||
if (fieldsCacheMap.has(index)) {
|
||||
return fieldsCacheMap.get(index);
|
||||
}
|
||||
|
||||
const fields = await searchStrategy.getFieldsForWildcard(req, index, capabilities);
|
||||
|
||||
fieldsCacheMap.set(index, fields);
|
||||
|
||||
return fields;
|
||||
};
|
||||
};
|
|
@ -27,7 +27,7 @@ import { formatKey } from './format_key';
|
|||
const getTimeSeries = (resp, series) =>
|
||||
_.get(resp, `aggregations.timeseries`) || _.get(resp, `aggregations.${series.id}.timeseries`);
|
||||
|
||||
export function getSplits(resp, panel, series, meta) {
|
||||
export async function getSplits(resp, panel, series, meta, extractFields) {
|
||||
if (!meta) {
|
||||
meta = _.get(resp, `aggregations.${series.id}.meta`);
|
||||
}
|
||||
|
@ -35,12 +35,17 @@ export function getSplits(resp, panel, series, meta) {
|
|||
const color = new Color(series.color);
|
||||
const metric = getLastMetric(series);
|
||||
const buckets = _.get(resp, `aggregations.${series.id}.buckets`);
|
||||
|
||||
const fieldsForMetaIndex = meta.index ? await extractFields(meta.index) : [];
|
||||
const splitByLabel = calculateLabel(metric, series.metrics, fieldsForMetaIndex);
|
||||
|
||||
if (buckets) {
|
||||
if (Array.isArray(buckets)) {
|
||||
const size = buckets.length;
|
||||
const colors = getSplitColors(series.color, size, series.split_color_mode);
|
||||
return buckets.map((bucket) => {
|
||||
bucket.id = `${series.id}:${bucket.key}`;
|
||||
bucket.splitByLabel = splitByLabel;
|
||||
bucket.label = formatKey(bucket.key, series);
|
||||
bucket.labelFormatted = bucket.key_as_string ? formatKey(bucket.key_as_string, series) : '';
|
||||
bucket.color = panel.type === 'top_n' ? color.string() : colors.shift();
|
||||
|
@ -72,10 +77,12 @@ export function getSplits(resp, panel, series, meta) {
|
|||
.forEach((m) => {
|
||||
mergeObj[m.id] = _.get(resp, `aggregations.${series.id}.${m.id}`);
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
id: series.id,
|
||||
label: series.label || calculateLabel(metric, series.metrics),
|
||||
splitByLabel,
|
||||
label: series.label || splitByLabel,
|
||||
color: color.string(),
|
||||
...mergeObj,
|
||||
meta,
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
import { getSplits } from './get_splits';
|
||||
|
||||
describe('getSplits(resp, panel, series)', () => {
|
||||
test('should return a splits for everything/filter group bys', () => {
|
||||
test('should return a splits for everything/filter group bys', async () => {
|
||||
const resp = {
|
||||
aggregations: {
|
||||
SERIES: {
|
||||
|
@ -40,19 +40,20 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
{ id: 'SIBAGG', type: 'avg_bucket', field: 'AVG' },
|
||||
],
|
||||
};
|
||||
expect(getSplits(resp, panel, series)).toEqual([
|
||||
expect(await getSplits(resp, panel, series, undefined)).toEqual([
|
||||
{
|
||||
id: 'SERIES',
|
||||
label: 'Overall Average of Average of cpu',
|
||||
meta: { bucketSize: 10 },
|
||||
color: 'rgb(255, 0, 0)',
|
||||
splitByLabel: 'Overall Average of Average of cpu',
|
||||
timeseries: { buckets: [] },
|
||||
SIBAGG: { value: 1 },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return a splits for terms group bys for top_n', () => {
|
||||
test('should return a splits for terms group bys for top_n', async () => {
|
||||
const resp = {
|
||||
aggregations: {
|
||||
SERIES: {
|
||||
|
@ -84,7 +85,7 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
],
|
||||
};
|
||||
const panel = { type: 'top_n' };
|
||||
expect(getSplits(resp, panel, series)).toEqual([
|
||||
expect(await getSplits(resp, panel, series)).toEqual([
|
||||
{
|
||||
id: 'SERIES:example-01',
|
||||
key: 'example-01',
|
||||
|
@ -92,6 +93,7 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
labelFormatted: '',
|
||||
meta: { bucketSize: 10 },
|
||||
color: 'rgb(255, 0, 0)',
|
||||
splitByLabel: 'Overall Average of Average of cpu',
|
||||
timeseries: { buckets: [] },
|
||||
SIBAGG: { value: 1 },
|
||||
},
|
||||
|
@ -102,13 +104,14 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
labelFormatted: '',
|
||||
meta: { bucketSize: 10 },
|
||||
color: 'rgb(255, 0, 0)',
|
||||
splitByLabel: 'Overall Average of Average of cpu',
|
||||
timeseries: { buckets: [] },
|
||||
SIBAGG: { value: 2 },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return a splits for terms group with label formatted by {{key}} placeholder', () => {
|
||||
test('should return a splits for terms group with label formatted by {{key}} placeholder', async () => {
|
||||
const resp = {
|
||||
aggregations: {
|
||||
SERIES: {
|
||||
|
@ -141,7 +144,7 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
],
|
||||
};
|
||||
const panel = { type: 'top_n' };
|
||||
expect(getSplits(resp, panel, series)).toEqual([
|
||||
expect(await getSplits(resp, panel, series)).toEqual([
|
||||
{
|
||||
id: 'SERIES:example-01',
|
||||
key: 'example-01',
|
||||
|
@ -149,6 +152,7 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
labelFormatted: '',
|
||||
meta: { bucketSize: 10 },
|
||||
color: 'rgb(255, 0, 0)',
|
||||
splitByLabel: 'Overall Average of Average of cpu',
|
||||
timeseries: { buckets: [] },
|
||||
SIBAGG: { value: 1 },
|
||||
},
|
||||
|
@ -159,13 +163,14 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
labelFormatted: '',
|
||||
meta: { bucketSize: 10 },
|
||||
color: 'rgb(255, 0, 0)',
|
||||
splitByLabel: 'Overall Average of Average of cpu',
|
||||
timeseries: { buckets: [] },
|
||||
SIBAGG: { value: 2 },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return a splits for terms group with labelFormatted if {{key}} placeholder is applied and key_as_string exists', () => {
|
||||
test('should return a splits for terms group with labelFormatted if {{key}} placeholder is applied and key_as_string exists', async () => {
|
||||
const resp = {
|
||||
aggregations: {
|
||||
SERIES: {
|
||||
|
@ -200,7 +205,8 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
],
|
||||
};
|
||||
const panel = { type: 'top_n' };
|
||||
expect(getSplits(resp, panel, series)).toEqual([
|
||||
|
||||
expect(await getSplits(resp, panel, series)).toEqual([
|
||||
{
|
||||
id: 'SERIES:example-01',
|
||||
key: 'example-01',
|
||||
|
@ -209,6 +215,7 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
labelFormatted: '--false--',
|
||||
meta: { bucketSize: 10 },
|
||||
color: 'rgb(255, 0, 0)',
|
||||
splitByLabel: 'Overall Average of Average of cpu',
|
||||
timeseries: { buckets: [] },
|
||||
SIBAGG: { value: 1 },
|
||||
},
|
||||
|
@ -220,6 +227,7 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
labelFormatted: '--true--',
|
||||
meta: { bucketSize: 10 },
|
||||
color: 'rgb(255, 0, 0)',
|
||||
splitByLabel: 'Overall Average of Average of cpu',
|
||||
timeseries: { buckets: [] },
|
||||
SIBAGG: { value: 2 },
|
||||
},
|
||||
|
@ -247,7 +255,7 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
},
|
||||
};
|
||||
|
||||
test('should return a splits with no color', () => {
|
||||
test('should return a splits with no color', async () => {
|
||||
const series = {
|
||||
id: 'SERIES',
|
||||
color: '#F00',
|
||||
|
@ -260,7 +268,8 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
],
|
||||
};
|
||||
const panel = { type: 'timeseries' };
|
||||
expect(getSplits(resp, panel, series)).toEqual([
|
||||
|
||||
expect(await getSplits(resp, panel, series)).toEqual([
|
||||
{
|
||||
id: 'SERIES:example-01',
|
||||
key: 'example-01',
|
||||
|
@ -268,6 +277,7 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
labelFormatted: '',
|
||||
meta: { bucketSize: 10 },
|
||||
color: undefined,
|
||||
splitByLabel: 'Overall Average of Average of cpu',
|
||||
timeseries: { buckets: [] },
|
||||
SIBAGG: { value: 1 },
|
||||
},
|
||||
|
@ -278,13 +288,14 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
labelFormatted: '',
|
||||
meta: { bucketSize: 10 },
|
||||
color: undefined,
|
||||
splitByLabel: 'Overall Average of Average of cpu',
|
||||
timeseries: { buckets: [] },
|
||||
SIBAGG: { value: 2 },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return gradient color', () => {
|
||||
test('should return gradient color', async () => {
|
||||
const series = {
|
||||
id: 'SERIES',
|
||||
color: '#F00',
|
||||
|
@ -298,7 +309,8 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
],
|
||||
};
|
||||
const panel = { type: 'timeseries' };
|
||||
expect(getSplits(resp, panel, series)).toEqual([
|
||||
|
||||
expect(await getSplits(resp, panel, series)).toEqual([
|
||||
expect.objectContaining({
|
||||
color: 'rgb(255, 0, 0)',
|
||||
}),
|
||||
|
@ -308,7 +320,7 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
test('should return rainbow color', () => {
|
||||
test('should return rainbow color', async () => {
|
||||
const series = {
|
||||
id: 'SERIES',
|
||||
color: '#F00',
|
||||
|
@ -322,7 +334,8 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
],
|
||||
};
|
||||
const panel = { type: 'timeseries' };
|
||||
expect(getSplits(resp, panel, series)).toEqual([
|
||||
|
||||
expect(await getSplits(resp, panel, series)).toEqual([
|
||||
expect.objectContaining({
|
||||
color: '#68BC00',
|
||||
}),
|
||||
|
@ -333,7 +346,7 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('should return a splits for filters group bys', () => {
|
||||
test('should return a splits for filters group bys', async () => {
|
||||
const resp = {
|
||||
aggregations: {
|
||||
SERIES: {
|
||||
|
@ -360,7 +373,8 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
metrics: [{ id: 'COUNT', type: 'count' }],
|
||||
};
|
||||
const panel = { type: 'timeseries' };
|
||||
expect(getSplits(resp, panel, series)).toEqual([
|
||||
|
||||
expect(await getSplits(resp, panel, series)).toEqual([
|
||||
{
|
||||
id: 'SERIES:filter-1',
|
||||
key: 'filter-1',
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
import { overwrite } from '../../helpers';
|
||||
import { getBucketSize } from '../../helpers/get_bucket_size';
|
||||
import { getTimerange } from '../../helpers/get_timerange';
|
||||
import { search } from '../../../../../../../plugins/data/server';
|
||||
import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server';
|
||||
|
||||
const { dateHistogramInterval } = search.aggs;
|
||||
|
||||
export function dateHistogram(
|
||||
|
@ -30,9 +31,10 @@ export function dateHistogram(
|
|||
esQueryConfig,
|
||||
indexPatternObject,
|
||||
capabilities,
|
||||
{ barTargetUiSettings }
|
||||
uiSettings
|
||||
) {
|
||||
return (next) => (doc) => {
|
||||
return (next) => async (doc) => {
|
||||
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
|
||||
const timeField = annotation.time_field;
|
||||
const { bucketSize, intervalString } = getBucketSize(
|
||||
req,
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import { getBucketSize } from '../../helpers/get_bucket_size';
|
||||
import { getTimerange } from '../../helpers/get_timerange';
|
||||
import { esQuery } from '../../../../../../data/server';
|
||||
import { esQuery, UI_SETTINGS } from '../../../../../../data/server';
|
||||
|
||||
export function query(
|
||||
req,
|
||||
|
@ -28,9 +28,10 @@ export function query(
|
|||
esQueryConfig,
|
||||
indexPattern,
|
||||
capabilities,
|
||||
{ barTargetUiSettings }
|
||||
uiSettings
|
||||
) {
|
||||
return (next) => (doc) => {
|
||||
return (next) => async (doc) => {
|
||||
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
|
||||
const timeField = annotation.time_field;
|
||||
const { bucketSize } = getBucketSize(req, 'auto', capabilities, barTargetUiSettings);
|
||||
const { from, to } = getTimerange(req);
|
||||
|
|
|
@ -23,6 +23,7 @@ export function topHits(req, panel, annotation) {
|
|||
return (next) => (doc) => {
|
||||
const fields = (annotation.fields && annotation.fields.split(/[,\s]+/)) || [];
|
||||
const timeField = annotation.time_field;
|
||||
|
||||
overwrite(doc, `aggs.${annotation.id}.aggs.hits.top_hits`, {
|
||||
sort: [
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ import { getBucketSize } from '../../helpers/get_bucket_size';
|
|||
import { offsetTime } from '../../offset_time';
|
||||
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
|
||||
import { isLastValueTimerangeMode } from '../../helpers/get_timerange_mode';
|
||||
import { search } from '../../../../../../../plugins/data/server';
|
||||
import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server';
|
||||
const { dateHistogramInterval } = search.aggs;
|
||||
|
||||
export function dateHistogram(
|
||||
|
@ -32,9 +32,12 @@ export function dateHistogram(
|
|||
esQueryConfig,
|
||||
indexPatternObject,
|
||||
capabilities,
|
||||
{ maxBarsUiSettings, barTargetUiSettings }
|
||||
uiSettings
|
||||
) {
|
||||
return (next) => (doc) => {
|
||||
return (next) => async (doc) => {
|
||||
const maxBarsUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS);
|
||||
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
|
||||
|
||||
const { timeField, interval, maxBars } = getIntervalAndTimefield(
|
||||
panel,
|
||||
series,
|
||||
|
@ -73,11 +76,10 @@ export function dateHistogram(
|
|||
? getDateHistogramForLastBucketMode()
|
||||
: getDateHistogramForEntireTimerangeMode();
|
||||
|
||||
// master
|
||||
|
||||
overwrite(doc, `aggs.${series.id}.meta`, {
|
||||
timeField,
|
||||
intervalString,
|
||||
index: indexPatternObject?.title,
|
||||
bucketSize,
|
||||
seriesId: series.id,
|
||||
});
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import { DefaultSearchCapabilities } from '../../../search_strategies/default_search_capabilities';
|
||||
import { dateHistogram } from './date_histogram';
|
||||
import { UI_SETTINGS } from '../../../../../../data/common';
|
||||
|
||||
describe('dateHistogram(req, panel, series)', () => {
|
||||
let panel;
|
||||
|
@ -51,20 +52,30 @@ describe('dateHistogram(req, panel, series)', () => {
|
|||
};
|
||||
indexPatternObject = {};
|
||||
capabilities = new DefaultSearchCapabilities(req);
|
||||
uiSettings = { maxBarsUiSettings: 100, barTargetUiSettings: 50 };
|
||||
uiSettings = {
|
||||
get: async (key) => (key === UI_SETTINGS.HISTOGRAM_MAX_BARS ? 100 : 50),
|
||||
};
|
||||
});
|
||||
|
||||
test('calls next when finished', () => {
|
||||
test('calls next when finished', async () => {
|
||||
const next = jest.fn();
|
||||
dateHistogram(req, panel, series, config, indexPatternObject, capabilities, uiSettings)(next)(
|
||||
{}
|
||||
);
|
||||
|
||||
await dateHistogram(
|
||||
req,
|
||||
panel,
|
||||
series,
|
||||
config,
|
||||
indexPatternObject,
|
||||
capabilities,
|
||||
uiSettings
|
||||
)(next)({});
|
||||
|
||||
expect(next.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('returns valid date histogram', () => {
|
||||
test('returns valid date histogram', async () => {
|
||||
const next = (doc) => doc;
|
||||
const doc = dateHistogram(
|
||||
const doc = await dateHistogram(
|
||||
req,
|
||||
panel,
|
||||
series,
|
||||
|
@ -102,10 +113,10 @@ describe('dateHistogram(req, panel, series)', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('returns valid date histogram (offset by 1h)', () => {
|
||||
test('returns valid date histogram (offset by 1h)', async () => {
|
||||
series.offset_time = '1h';
|
||||
const next = (doc) => doc;
|
||||
const doc = dateHistogram(
|
||||
const doc = await dateHistogram(
|
||||
req,
|
||||
panel,
|
||||
series,
|
||||
|
@ -143,13 +154,13 @@ describe('dateHistogram(req, panel, series)', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('returns valid date histogram with overridden index pattern', () => {
|
||||
test('returns valid date histogram with overridden index pattern', async () => {
|
||||
series.override_index_pattern = 1;
|
||||
series.series_index_pattern = '*';
|
||||
series.series_time_field = 'timestamp';
|
||||
series.series_interval = '20s';
|
||||
const next = (doc) => doc;
|
||||
const doc = dateHistogram(
|
||||
const doc = await dateHistogram(
|
||||
req,
|
||||
panel,
|
||||
series,
|
||||
|
@ -188,12 +199,12 @@ describe('dateHistogram(req, panel, series)', () => {
|
|||
});
|
||||
|
||||
describe('dateHistogram for entire time range mode', () => {
|
||||
test('should ignore entire range mode for timeseries', () => {
|
||||
test('should ignore entire range mode for timeseries', async () => {
|
||||
panel.time_range_mode = 'entire_time_range';
|
||||
panel.type = 'timeseries';
|
||||
|
||||
const next = (doc) => doc;
|
||||
const doc = dateHistogram(
|
||||
const doc = await dateHistogram(
|
||||
req,
|
||||
panel,
|
||||
series,
|
||||
|
@ -207,11 +218,11 @@ describe('dateHistogram(req, panel, series)', () => {
|
|||
expect(doc.aggs.test.aggs.timeseries.date_histogram).toBeDefined();
|
||||
});
|
||||
|
||||
test('should returns valid date histogram for entire range mode', () => {
|
||||
test('should returns valid date histogram for entire range mode', async () => {
|
||||
panel.time_range_mode = 'entire_time_range';
|
||||
|
||||
const next = (doc) => doc;
|
||||
const doc = dateHistogram(
|
||||
const doc = await dateHistogram(
|
||||
req,
|
||||
panel,
|
||||
series,
|
||||
|
|
|
@ -17,11 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
const filter = (metric) => metric.type === 'filter_ratio';
|
||||
import { bucketTransform } from '../../helpers/bucket_transform';
|
||||
import { overwrite } from '../../helpers';
|
||||
import { esQuery } from '../../../../../../data/server';
|
||||
|
||||
const filter = (metric) => metric.type === 'filter_ratio';
|
||||
|
||||
export function ratios(req, panel, series, esQueryConfig, indexPatternObject) {
|
||||
return (next) => (doc) => {
|
||||
if (series.metrics.some(filter)) {
|
||||
|
|
|
@ -20,6 +20,7 @@ import { overwrite } from '../../helpers';
|
|||
import { getBucketSize } from '../../helpers/get_bucket_size';
|
||||
import { bucketTransform } from '../../helpers/bucket_transform';
|
||||
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
|
||||
import { UI_SETTINGS } from '../../../../../../data/common';
|
||||
|
||||
export function metricBuckets(
|
||||
req,
|
||||
|
@ -28,9 +29,11 @@ export function metricBuckets(
|
|||
esQueryConfig,
|
||||
indexPatternObject,
|
||||
capabilities,
|
||||
{ barTargetUiSettings }
|
||||
uiSettings
|
||||
) {
|
||||
return (next) => (doc) => {
|
||||
return (next) => async (doc) => {
|
||||
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
|
||||
|
||||
const { interval } = getIntervalAndTimefield(panel, series, indexPatternObject);
|
||||
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
|
||||
|
||||
|
|
|
@ -63,20 +63,20 @@ describe('metricBuckets(req, panel, series)', () => {
|
|||
{},
|
||||
undefined,
|
||||
{
|
||||
barTargetUiSettings: 50,
|
||||
get: async () => 50,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('calls next when finished', () => {
|
||||
test('calls next when finished', async () => {
|
||||
const next = jest.fn();
|
||||
metricBucketsProcessor(next)({});
|
||||
await metricBucketsProcessor(next)({});
|
||||
expect(next.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('returns metric aggs', () => {
|
||||
test('returns metric aggs', async () => {
|
||||
const next = (doc) => doc;
|
||||
const doc = metricBucketsProcessor(next)({});
|
||||
const doc = await metricBucketsProcessor(next)({});
|
||||
|
||||
expect(doc).toEqual({
|
||||
aggs: {
|
||||
|
|
|
@ -21,6 +21,7 @@ import { getBucketSize } from '../../helpers/get_bucket_size';
|
|||
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
|
||||
import { bucketTransform } from '../../helpers/bucket_transform';
|
||||
import { overwrite } from '../../helpers';
|
||||
import { UI_SETTINGS } from '../../../../../../data/common';
|
||||
|
||||
export const filter = (metric) => metric.type === 'positive_rate';
|
||||
|
||||
|
@ -29,7 +30,11 @@ export const createPositiveRate = (doc, intervalString, aggRoot) => (metric) =>
|
|||
const derivativeFn = bucketTransform.derivative;
|
||||
const positiveOnlyFn = bucketTransform.positive_only;
|
||||
|
||||
const maxMetric = { id: `${metric.id}-positive-rate-max`, type: 'max', field: metric.field };
|
||||
const maxMetric = {
|
||||
id: `${metric.id}-positive-rate-max`,
|
||||
type: 'max',
|
||||
field: metric.field,
|
||||
};
|
||||
const derivativeMetric = {
|
||||
id: `${metric.id}-positive-rate-derivative`,
|
||||
type: 'derivative',
|
||||
|
@ -64,9 +69,11 @@ export function positiveRate(
|
|||
esQueryConfig,
|
||||
indexPatternObject,
|
||||
capabilities,
|
||||
{ barTargetUiSettings }
|
||||
uiSettings
|
||||
) {
|
||||
return (next) => (doc) => {
|
||||
return (next) => async (doc) => {
|
||||
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
|
||||
|
||||
const { interval } = getIntervalAndTimefield(panel, series, indexPatternObject);
|
||||
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
|
||||
|
||||
|
|
|
@ -51,19 +51,21 @@ describe('positiveRate(req, panel, series)', () => {
|
|||
},
|
||||
};
|
||||
uiSettings = {
|
||||
barTargetUiSettings: 50,
|
||||
get: async () => 50,
|
||||
};
|
||||
});
|
||||
|
||||
test('calls next when finished', () => {
|
||||
test('calls next when finished', async () => {
|
||||
const next = jest.fn();
|
||||
positiveRate(req, panel, series, {}, {}, undefined, uiSettings)(next)({});
|
||||
await positiveRate(req, panel, series, {}, {}, undefined, uiSettings)(next)({});
|
||||
|
||||
expect(next.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('returns positive rate aggs', () => {
|
||||
test('returns positive rate aggs', async () => {
|
||||
const next = (doc) => doc;
|
||||
const doc = positiveRate(req, panel, series, {}, {}, undefined, uiSettings)(next)({});
|
||||
const doc = await positiveRate(req, panel, series, {}, {}, undefined, uiSettings)(next)({});
|
||||
|
||||
expect(doc).toEqual({
|
||||
aggs: {
|
||||
test: {
|
||||
|
|
|
@ -21,6 +21,7 @@ import { overwrite } from '../../helpers';
|
|||
import { getBucketSize } from '../../helpers/get_bucket_size';
|
||||
import { bucketTransform } from '../../helpers/bucket_transform';
|
||||
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
|
||||
import { UI_SETTINGS } from '../../../../../../data/common';
|
||||
|
||||
export function siblingBuckets(
|
||||
req,
|
||||
|
@ -29,9 +30,10 @@ export function siblingBuckets(
|
|||
esQueryConfig,
|
||||
indexPatternObject,
|
||||
capabilities,
|
||||
{ barTargetUiSettings }
|
||||
uiSettings
|
||||
) {
|
||||
return (next) => (doc) => {
|
||||
return (next) => async (doc) => {
|
||||
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
|
||||
const { interval } = getIntervalAndTimefield(panel, series, indexPatternObject);
|
||||
const { bucketSize } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
|
||||
|
||||
|
|
|
@ -56,19 +56,19 @@ describe('siblingBuckets(req, panel, series)', () => {
|
|||
},
|
||||
};
|
||||
uiSettings = {
|
||||
barTargetUiSettings: 50,
|
||||
get: async () => 50,
|
||||
};
|
||||
});
|
||||
|
||||
test('calls next when finished', () => {
|
||||
test('calls next when finished', async () => {
|
||||
const next = jest.fn();
|
||||
siblingBuckets(req, panel, series, {}, {}, undefined, uiSettings)(next)({});
|
||||
await siblingBuckets(req, panel, series, {}, {}, undefined, uiSettings)(next)({});
|
||||
expect(next.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('returns sibling aggs', () => {
|
||||
test('returns sibling aggs', async () => {
|
||||
const next = (doc) => doc;
|
||||
const doc = siblingBuckets(req, panel, series, {}, {}, undefined, uiSettings)(next)({});
|
||||
const doc = await siblingBuckets(req, panel, series, {}, {}, undefined, uiSettings)(next)({});
|
||||
|
||||
expect(doc).toEqual({
|
||||
aggs: {
|
||||
|
|
|
@ -25,9 +25,12 @@ import { bucketTransform } from '../../helpers/bucket_transform';
|
|||
export function splitByTerms(req, panel, series) {
|
||||
return (next) => (doc) => {
|
||||
if (series.split_mode === 'terms' && series.terms_field) {
|
||||
const termsField = series.terms_field;
|
||||
const orderByTerms = series.terms_order_by;
|
||||
|
||||
const direction = series.terms_direction || 'desc';
|
||||
const metric = series.metrics.find((item) => item.id === series.terms_order_by);
|
||||
overwrite(doc, `aggs.${series.id}.terms.field`, series.terms_field);
|
||||
const metric = series.metrics.find((item) => item.id === orderByTerms);
|
||||
overwrite(doc, `aggs.${series.id}.terms.field`, termsField);
|
||||
overwrite(doc, `aggs.${series.id}.terms.size`, series.terms_size);
|
||||
if (series.terms_include) {
|
||||
overwrite(doc, `aggs.${series.id}.terms.include`, series.terms_include);
|
||||
|
@ -36,16 +39,16 @@ export function splitByTerms(req, panel, series) {
|
|||
overwrite(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 sortAggKey = `${orderByTerms}-SORT`;
|
||||
const fn = bucketTransform[metric.type];
|
||||
const bucketPath = getBucketsPath(series.terms_order_by, series.metrics).replace(
|
||||
series.terms_order_by,
|
||||
const bucketPath = getBucketsPath(orderByTerms, series.metrics).replace(
|
||||
orderByTerms,
|
||||
sortAggKey
|
||||
);
|
||||
overwrite(doc, `aggs.${series.id}.terms.order`, { [bucketPath]: direction });
|
||||
overwrite(doc, `aggs.${series.id}.aggs`, { [sortAggKey]: fn(metric) });
|
||||
} else if (['_key', '_count'].includes(series.terms_order_by)) {
|
||||
overwrite(doc, `aggs.${series.id}.terms.order`, { [series.terms_order_by]: direction });
|
||||
} else if (['_key', '_count'].includes(orderByTerms)) {
|
||||
overwrite(doc, `aggs.${series.id}.terms.order`, { [orderByTerms]: direction });
|
||||
} else {
|
||||
overwrite(doc, `aggs.${series.id}.terms.order`, { _count: direction });
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import { isLastValueTimerangeMode } from '../../helpers/get_timerange_mode';
|
|||
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
|
||||
import { getTimerange } from '../../helpers/get_timerange';
|
||||
import { calculateAggRoot } from './calculate_agg_root';
|
||||
import { search } from '../../../../../../../plugins/data/server';
|
||||
import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server';
|
||||
const { dateHistogramInterval } = search.aggs;
|
||||
|
||||
export function dateHistogram(
|
||||
|
@ -32,12 +32,14 @@ export function dateHistogram(
|
|||
esQueryConfig,
|
||||
indexPatternObject,
|
||||
capabilities,
|
||||
{ barTargetUiSettings }
|
||||
uiSettings
|
||||
) {
|
||||
return (next) => (doc) => {
|
||||
return (next) => async (doc) => {
|
||||
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
|
||||
const { timeField, interval } = getIntervalAndTimefield(panel, {}, indexPatternObject);
|
||||
const meta = {
|
||||
timeField,
|
||||
index: indexPatternObject?.title,
|
||||
};
|
||||
|
||||
const getDateHistogramForLastBucketMode = () => {
|
||||
|
@ -65,7 +67,7 @@ export function dateHistogram(
|
|||
});
|
||||
|
||||
overwrite(doc, aggRoot.replace(/\.aggs$/, '.meta'), {
|
||||
timeField,
|
||||
...meta,
|
||||
intervalString,
|
||||
bucketSize,
|
||||
});
|
||||
|
|
|
@ -17,12 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
const filter = (metric) => metric.type === 'filter_ratio';
|
||||
import { esQuery } from '../../../../../../data/server';
|
||||
import { bucketTransform } from '../../helpers/bucket_transform';
|
||||
import { overwrite } from '../../helpers';
|
||||
import { calculateAggRoot } from './calculate_agg_root';
|
||||
|
||||
const filter = (metric) => metric.type === 'filter_ratio';
|
||||
|
||||
export function ratios(req, panel, esQueryConfig, indexPatternObject) {
|
||||
return (next) => (doc) => {
|
||||
panel.series.forEach((column) => {
|
||||
|
|
|
@ -22,6 +22,7 @@ import { getBucketSize } from '../../helpers/get_bucket_size';
|
|||
import { bucketTransform } from '../../helpers/bucket_transform';
|
||||
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
|
||||
import { calculateAggRoot } from './calculate_agg_root';
|
||||
import { UI_SETTINGS } from '../../../../../../data/common';
|
||||
|
||||
export function metricBuckets(
|
||||
req,
|
||||
|
@ -29,9 +30,10 @@ export function metricBuckets(
|
|||
esQueryConfig,
|
||||
indexPatternObject,
|
||||
capabilities,
|
||||
{ barTargetUiSettings }
|
||||
uiSettings
|
||||
) {
|
||||
return (next) => (doc) => {
|
||||
return (next) => async (doc) => {
|
||||
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
|
||||
const { interval } = getIntervalAndTimefield(panel, {}, indexPatternObject);
|
||||
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import { bucketTransform } from '../../helpers/bucket_transform';
|
|||
export function pivot(req, panel) {
|
||||
return (next) => (doc) => {
|
||||
const { sort } = req.payload.state;
|
||||
|
||||
if (panel.pivot_id) {
|
||||
overwrite(doc, 'aggs.pivot.terms.field', panel.pivot_id);
|
||||
overwrite(doc, 'aggs.pivot.terms.size', panel.pivot_rows);
|
||||
|
|
|
@ -21,6 +21,7 @@ import { getBucketSize } from '../../helpers/get_bucket_size';
|
|||
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
|
||||
import { calculateAggRoot } from './calculate_agg_root';
|
||||
import { createPositiveRate, filter } from '../series/positive_rate';
|
||||
import { UI_SETTINGS } from '../../../../../../data/common';
|
||||
|
||||
export function positiveRate(
|
||||
req,
|
||||
|
@ -28,9 +29,10 @@ export function positiveRate(
|
|||
esQueryConfig,
|
||||
indexPatternObject,
|
||||
capabilities,
|
||||
{ barTargetUiSettings }
|
||||
uiSettings
|
||||
) {
|
||||
return (next) => (doc) => {
|
||||
return (next) => async (doc) => {
|
||||
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
|
||||
const { interval } = getIntervalAndTimefield(panel, {}, indexPatternObject);
|
||||
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import { getBucketSize } from '../../helpers/get_bucket_size';
|
|||
import { bucketTransform } from '../../helpers/bucket_transform';
|
||||
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
|
||||
import { calculateAggRoot } from './calculate_agg_root';
|
||||
import { UI_SETTINGS } from '../../../../../../data/common';
|
||||
|
||||
export function siblingBuckets(
|
||||
req,
|
||||
|
@ -29,9 +30,10 @@ export function siblingBuckets(
|
|||
esQueryConfig,
|
||||
indexPatternObject,
|
||||
capabilities,
|
||||
{ barTargetUiSettings }
|
||||
uiSettings
|
||||
) {
|
||||
return (next) => (doc) => {
|
||||
return (next) => async (doc) => {
|
||||
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
|
||||
const { interval } = getIntervalAndTimefield(panel, {}, indexPatternObject);
|
||||
const { bucketSize } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
|
||||
|
||||
|
|
|
@ -25,8 +25,8 @@ import { getSplits } from '../../helpers/get_splits';
|
|||
import { mapBucket } from '../../helpers/map_bucket';
|
||||
import { evaluate } from 'tinymath';
|
||||
|
||||
export function mathAgg(resp, panel, series, meta) {
|
||||
return (next) => (results) => {
|
||||
export function mathAgg(resp, panel, series, meta, extractFields) {
|
||||
return (next) => async (results) => {
|
||||
const mathMetric = last(series.metrics);
|
||||
if (mathMetric.type !== 'math') return next(results);
|
||||
// Filter the results down to only the ones that match the series.id. Sometimes
|
||||
|
@ -38,7 +38,7 @@ export function mathAgg(resp, panel, series, meta) {
|
|||
return true;
|
||||
});
|
||||
const decoration = getDefaultDecoration(series);
|
||||
const splits = getSplits(resp, panel, series, meta);
|
||||
const splits = await getSplits(resp, panel, series, meta, extractFields);
|
||||
const mathSeries = splits.map((split) => {
|
||||
if (mathMetric.variables.length) {
|
||||
// Gather the data for the splits. The data will either be a sibling agg or
|
||||
|
|
|
@ -91,15 +91,16 @@ describe('math(resp, panel, series)', () => {
|
|||
};
|
||||
});
|
||||
|
||||
test('calls next when finished', () => {
|
||||
test('calls next when finished', async () => {
|
||||
const next = jest.fn();
|
||||
mathAgg(resp, panel, series)(next)([]);
|
||||
await mathAgg(resp, panel, series)(next)([]);
|
||||
expect(next.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('creates a series', () => {
|
||||
const next = mathAgg(resp, panel, series)((results) => results);
|
||||
const results = stdMetric(resp, panel, series)(next)([]);
|
||||
test('creates a series', async () => {
|
||||
const next = await mathAgg(resp, panel, series)((results) => results);
|
||||
const results = await stdMetric(resp, panel, series)(next)([]);
|
||||
|
||||
expect(results).toHaveLength(1);
|
||||
|
||||
expect(results[0]).toEqual({
|
||||
|
@ -118,12 +119,12 @@ describe('math(resp, panel, series)', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('turns division by zero into null values', () => {
|
||||
test('turns division by zero into null values', async () => {
|
||||
resp.aggregations.test.buckets[0].timeseries.buckets[0].mincpu = 0;
|
||||
const next = mathAgg(resp, panel, series)((results) => results);
|
||||
const results = stdMetric(resp, panel, series)(next)([]);
|
||||
expect(results).toHaveLength(1);
|
||||
const next = await mathAgg(resp, panel, series)((results) => results);
|
||||
const results = await stdMetric(resp, panel, series)(next)([]);
|
||||
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
data: [
|
||||
|
@ -134,15 +135,35 @@ describe('math(resp, panel, series)', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('throws on actual tinymath expression errors', () => {
|
||||
test('throws on actual tinymath expression errors #1', async () => {
|
||||
series.metrics[2].script = 'notExistingFn(params.a)';
|
||||
expect(() =>
|
||||
stdMetric(resp, panel, series)(mathAgg(resp, panel, series)((results) => results))([])
|
||||
).toThrow();
|
||||
|
||||
try {
|
||||
await stdMetric(
|
||||
resp,
|
||||
panel,
|
||||
series
|
||||
)(await mathAgg(resp, panel, series)((results) => results))([]);
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual(
|
||||
'Failed to parse expression. Expected "*", "+", "-", "/", or end of input but "(" found.'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('throws on actual tinymath expression errors #2', async () => {
|
||||
series.metrics[2].script = 'divide(params.a, params.b';
|
||||
expect(() =>
|
||||
stdMetric(resp, panel, series)(mathAgg(resp, panel, series)((results) => results))([])
|
||||
).toThrow();
|
||||
|
||||
try {
|
||||
await stdMetric(
|
||||
resp,
|
||||
panel,
|
||||
series
|
||||
)(await mathAgg(resp, panel, series)((results) => results))([]);
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual(
|
||||
'Failed to parse expression. Expected "*", "+", "-", "/", or end of input but "(" found.'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,15 +23,15 @@ import { getSplits } from '../../helpers/get_splits';
|
|||
import { getLastMetric } from '../../helpers/get_last_metric';
|
||||
import { METRIC_TYPES } from '../../../../../common/metric_types';
|
||||
|
||||
export function percentile(resp, panel, series, meta) {
|
||||
return (next) => (results) => {
|
||||
export function percentile(resp, panel, series, meta, extractFields) {
|
||||
return (next) => async (results) => {
|
||||
const metric = getLastMetric(series);
|
||||
|
||||
if (metric.type !== METRIC_TYPES.PERCENTILE) {
|
||||
return next(results);
|
||||
}
|
||||
|
||||
getSplits(resp, panel, series, meta).forEach((split) => {
|
||||
(await getSplits(resp, panel, series, meta, extractFields)).forEach((split) => {
|
||||
metric.percentiles.forEach((percentile) => {
|
||||
const percentileValue = percentile.value ? percentile.value : 0;
|
||||
const id = `${split.id}:${percentile.id}`;
|
||||
|
|
|
@ -80,15 +80,18 @@ describe('percentile(resp, panel, series)', () => {
|
|||
};
|
||||
});
|
||||
|
||||
test('calls next when finished', () => {
|
||||
test('calls next when finished', async () => {
|
||||
const next = jest.fn();
|
||||
percentile(resp, panel, series)(next)([]);
|
||||
|
||||
await percentile(resp, panel, series, {})(next)([]);
|
||||
|
||||
expect(next.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('creates a series', () => {
|
||||
test('creates a series', async () => {
|
||||
const next = (results) => results;
|
||||
const results = percentile(resp, panel, series)(next)([]);
|
||||
const results = await percentile(resp, panel, series, {})(next)([]);
|
||||
|
||||
expect(results).toHaveLength(2);
|
||||
|
||||
expect(results[0]).toHaveProperty('id', 'test:10-90');
|
||||
|
|
|
@ -23,15 +23,15 @@ import { getLastMetric } from '../../helpers/get_last_metric';
|
|||
import { toPercentileNumber } from '../../../../../common/to_percentile_number';
|
||||
import { METRIC_TYPES } from '../../../../../common/metric_types';
|
||||
|
||||
export function percentileRank(resp, panel, series, meta) {
|
||||
return (next) => (results) => {
|
||||
export function percentileRank(resp, panel, series, meta, extractFields) {
|
||||
return (next) => async (results) => {
|
||||
const metric = getLastMetric(series);
|
||||
|
||||
if (metric.type !== METRIC_TYPES.PERCENTILE_RANK) {
|
||||
return next(results);
|
||||
}
|
||||
|
||||
getSplits(resp, panel, series, meta).forEach((split) => {
|
||||
(await getSplits(resp, panel, series, meta, extractFields)).forEach((split) => {
|
||||
(metric.values || []).forEach((percentileRank, index) => {
|
||||
const data = split.timeseries.buckets.map((bucket) => [
|
||||
bucket.key,
|
||||
|
|
|
@ -22,8 +22,8 @@ import _ from 'lodash';
|
|||
import { getDefaultDecoration } from '../../helpers/get_default_decoration';
|
||||
import { calculateLabel } from '../../../../../common/calculate_label';
|
||||
|
||||
export function seriesAgg(resp, panel, series) {
|
||||
return (next) => (results) => {
|
||||
export function seriesAgg(resp, panel, series, meta, extractFields) {
|
||||
return (next) => async (results) => {
|
||||
if (series.metrics.some((m) => m.type === 'series_agg')) {
|
||||
const decoration = getDefaultDecoration(series);
|
||||
|
||||
|
@ -43,9 +43,14 @@ export function seriesAgg(resp, panel, series) {
|
|||
const fn = SeriesAgg[m.function];
|
||||
return (fn && fn(acc)) || acc;
|
||||
}, targetSeries);
|
||||
|
||||
const fieldsForMetaIndex = meta.index ? await extractFields(meta.index) : [];
|
||||
|
||||
results.push({
|
||||
id: `${series.id}`,
|
||||
label: series.label || calculateLabel(_.last(series.metrics), series.metrics),
|
||||
label:
|
||||
series.label ||
|
||||
calculateLabel(_.last(series.metrics), series.metrics, fieldsForMetaIndex),
|
||||
color: series.color,
|
||||
data: _.first(data),
|
||||
...decoration,
|
||||
|
|
|
@ -91,15 +91,16 @@ describe('seriesAgg(resp, panel, series)', () => {
|
|||
};
|
||||
});
|
||||
|
||||
test('calls next when finished', () => {
|
||||
test('calls next when finished', async () => {
|
||||
const next = jest.fn();
|
||||
seriesAgg(resp, panel, series)(next)([]);
|
||||
await seriesAgg(resp, panel, series, {})(next)([]);
|
||||
expect(next.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('creates a series', () => {
|
||||
const next = seriesAgg(resp, panel, series)((results) => results);
|
||||
const results = stdMetric(resp, panel, series)(next)([]);
|
||||
test('creates a series', async () => {
|
||||
const next = await seriesAgg(resp, panel, series, {})((results) => results);
|
||||
const results = await stdMetric(resp, panel, series, {})(next)([]);
|
||||
|
||||
expect(results).toHaveLength(1);
|
||||
|
||||
expect(results[0]).toEqual({
|
||||
|
|
|
@ -20,36 +20,38 @@
|
|||
import { getAggValue, getLastMetric, getSplits } from '../../helpers';
|
||||
import { METRIC_TYPES } from '../../../../../common/metric_types';
|
||||
|
||||
export function stdDeviationBands(resp, panel, series, meta) {
|
||||
return (next) => (results) => {
|
||||
export function stdDeviationBands(resp, panel, series, meta, extractFields) {
|
||||
return (next) => async (results) => {
|
||||
const metric = getLastMetric(series);
|
||||
if (metric.type === METRIC_TYPES.STD_DEVIATION && metric.mode === 'band') {
|
||||
getSplits(resp, panel, series, meta).forEach(({ id, color, label, timeseries }) => {
|
||||
const data = timeseries.buckets.map((bucket) => [
|
||||
bucket.key,
|
||||
getAggValue(bucket, { ...metric, mode: 'upper' }),
|
||||
getAggValue(bucket, { ...metric, mode: 'lower' }),
|
||||
]);
|
||||
(await getSplits(resp, panel, series, meta, extractFields)).forEach(
|
||||
({ id, color, label, timeseries }) => {
|
||||
const data = timeseries.buckets.map((bucket) => [
|
||||
bucket.key,
|
||||
getAggValue(bucket, { ...metric, mode: 'upper' }),
|
||||
getAggValue(bucket, { ...metric, mode: 'lower' }),
|
||||
]);
|
||||
|
||||
results.push({
|
||||
id,
|
||||
label,
|
||||
color,
|
||||
data,
|
||||
lines: {
|
||||
show: series.chart_type === 'line',
|
||||
fill: 0.5,
|
||||
lineWidth: 0,
|
||||
mode: 'band',
|
||||
},
|
||||
bars: {
|
||||
show: series.chart_type === 'bar',
|
||||
fill: 0.5,
|
||||
mode: 'band',
|
||||
},
|
||||
points: { show: false },
|
||||
});
|
||||
});
|
||||
results.push({
|
||||
id,
|
||||
label,
|
||||
color,
|
||||
data,
|
||||
lines: {
|
||||
show: series.chart_type === 'line',
|
||||
fill: 0.5,
|
||||
lineWidth: 0,
|
||||
mode: 'band',
|
||||
},
|
||||
bars: {
|
||||
show: series.chart_type === 'bar',
|
||||
fill: 0.5,
|
||||
mode: 'band',
|
||||
},
|
||||
points: { show: false },
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
return next(results);
|
||||
};
|
||||
|
|
|
@ -77,15 +77,15 @@ describe('stdDeviationBands(resp, panel, series)', () => {
|
|||
};
|
||||
});
|
||||
|
||||
test('calls next when finished', () => {
|
||||
test('calls next when finished', async () => {
|
||||
const next = jest.fn();
|
||||
stdDeviationBands(resp, panel, series)(next)([]);
|
||||
await stdDeviationBands(resp, panel, series, {})(next)([]);
|
||||
expect(next.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('creates a series', () => {
|
||||
test('creates a series', async () => {
|
||||
const next = (results) => results;
|
||||
const results = stdDeviationBands(resp, panel, series)(next)([]);
|
||||
const results = await stdDeviationBands(resp, panel, series, {})(next)([]);
|
||||
expect(results).toHaveLength(1);
|
||||
|
||||
expect(results[0]).toEqual({
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
|
||||
import { getSplits, getLastMetric, getSiblingAggValue } from '../../helpers';
|
||||
|
||||
export function stdDeviationSibling(resp, panel, series, meta) {
|
||||
return (next) => (results) => {
|
||||
export function stdDeviationSibling(resp, panel, series, meta, extractFields) {
|
||||
return (next) => async (results) => {
|
||||
const metric = getLastMetric(series);
|
||||
if (metric.mode === 'band' && metric.type === 'std_deviation_bucket') {
|
||||
getSplits(resp, panel, series, meta).forEach((split) => {
|
||||
(await getSplits(resp, panel, series, meta, extractFields)).forEach((split) => {
|
||||
const data = split.timeseries.buckets.map((bucket) => [
|
||||
bucket.key,
|
||||
getSiblingAggValue(split, { ...metric, mode: 'upper' }),
|
||||
|
|
|
@ -77,15 +77,15 @@ describe('stdDeviationSibling(resp, panel, series)', () => {
|
|||
};
|
||||
});
|
||||
|
||||
test('calls next when finished', () => {
|
||||
test('calls next when finished', async () => {
|
||||
const next = jest.fn();
|
||||
stdDeviationSibling(resp, panel, series)(next)([]);
|
||||
await stdDeviationSibling(resp, panel, series, {})(next)([]);
|
||||
expect(next.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('creates a series', () => {
|
||||
test('creates a series', async () => {
|
||||
const next = (results) => results;
|
||||
const results = stdDeviationSibling(resp, panel, series)(next)([]);
|
||||
const results = await stdDeviationSibling(resp, panel, series, {})(next)([]);
|
||||
expect(results).toHaveLength(1);
|
||||
|
||||
expect(results[0]).toEqual({
|
||||
|
|
|
@ -23,8 +23,8 @@ import { getLastMetric } from '../../helpers/get_last_metric';
|
|||
import { mapBucket } from '../../helpers/map_bucket';
|
||||
import { METRIC_TYPES } from '../../../../../common/metric_types';
|
||||
|
||||
export function stdMetric(resp, panel, series, meta) {
|
||||
return (next) => (results) => {
|
||||
export function stdMetric(resp, panel, series, meta, extractFields) {
|
||||
return (next) => async (results) => {
|
||||
const metric = getLastMetric(series);
|
||||
if (metric.type === METRIC_TYPES.STD_DEVIATION && metric.mode === 'band') {
|
||||
return next(results);
|
||||
|
@ -35,17 +35,20 @@ export function stdMetric(resp, panel, series, meta) {
|
|||
}
|
||||
if (/_bucket$/.test(metric.type)) return next(results);
|
||||
const decoration = getDefaultDecoration(series);
|
||||
getSplits(resp, panel, series, meta).forEach((split) => {
|
||||
|
||||
(await getSplits(resp, panel, series, meta, extractFields)).forEach((split) => {
|
||||
const data = split.timeseries.buckets.map(mapBucket(metric));
|
||||
results.push({
|
||||
id: `${split.id}`,
|
||||
label: split.label,
|
||||
splitByLabel: split.splitByLabel,
|
||||
labelFormatted: split.labelFormatted,
|
||||
color: split.color,
|
||||
data,
|
||||
...decoration,
|
||||
});
|
||||
});
|
||||
|
||||
return next(results);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -58,32 +58,38 @@ describe('stdMetric(resp, panel, series)', () => {
|
|||
};
|
||||
});
|
||||
|
||||
test('calls next when finished', () => {
|
||||
test('calls next when finished', async () => {
|
||||
const next = jest.fn();
|
||||
stdMetric(resp, panel, series)(next)([]);
|
||||
await stdMetric(resp, panel, series, {})(next)([]);
|
||||
|
||||
expect(next.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('calls next when finished (percentile)', () => {
|
||||
test('calls next when finished (percentile)', async () => {
|
||||
series.metrics[0].type = 'percentile';
|
||||
|
||||
const next = jest.fn((d) => d);
|
||||
const results = stdMetric(resp, panel, series)(next)([]);
|
||||
const results = await stdMetric(resp, panel, series, {})(next)([]);
|
||||
|
||||
expect(next.mock.calls.length).toEqual(1);
|
||||
expect(results).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('calls next when finished (std_deviation band)', () => {
|
||||
test('calls next when finished (std_deviation band)', async () => {
|
||||
series.metrics[0].type = 'std_deviation';
|
||||
series.metrics[0].mode = 'band';
|
||||
|
||||
const next = jest.fn((d) => d);
|
||||
const results = stdMetric(resp, panel, series)(next)([]);
|
||||
const results = await stdMetric(resp, panel, series, {})(next)([]);
|
||||
|
||||
expect(next.mock.calls.length).toEqual(1);
|
||||
expect(results).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('creates a series', () => {
|
||||
test('creates a series', async () => {
|
||||
const next = (results) => results;
|
||||
const results = stdMetric(resp, panel, series)(next)([]);
|
||||
const results = await stdMetric(resp, panel, series, {})(next)([]);
|
||||
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0]).toHaveProperty('color', 'rgb(255, 0, 0)');
|
||||
expect(results[0]).toHaveProperty('id', 'test');
|
||||
|
|
|
@ -22,15 +22,15 @@ import { getSplits } from '../../helpers/get_splits';
|
|||
import { getLastMetric } from '../../helpers/get_last_metric';
|
||||
import { getSiblingAggValue } from '../../helpers/get_sibling_agg_value';
|
||||
|
||||
export function stdSibling(resp, panel, series, meta) {
|
||||
return (next) => (results) => {
|
||||
export function stdSibling(resp, panel, series, meta, extractFields) {
|
||||
return (next) => async (results) => {
|
||||
const metric = getLastMetric(series);
|
||||
|
||||
if (!/_bucket$/.test(metric.type)) return next(results);
|
||||
if (metric.type === 'std_deviation_bucket' && metric.mode === 'band') return next(results);
|
||||
|
||||
const decoration = getDefaultDecoration(series);
|
||||
getSplits(resp, panel, series, meta).forEach((split) => {
|
||||
(await getSplits(resp, panel, series, meta, extractFields)).forEach((split) => {
|
||||
const data = split.timeseries.buckets.map((bucket) => {
|
||||
return [bucket.key, getSiblingAggValue(split, metric)];
|
||||
});
|
||||
|
@ -42,6 +42,7 @@ export function stdSibling(resp, panel, series, meta) {
|
|||
...decoration,
|
||||
});
|
||||
});
|
||||
|
||||
return next(results);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -72,23 +72,23 @@ describe('stdSibling(resp, panel, series)', () => {
|
|||
};
|
||||
});
|
||||
|
||||
test('calls next when finished', () => {
|
||||
test('calls next when finished', async () => {
|
||||
const next = jest.fn();
|
||||
stdSibling(resp, panel, series)(next)([]);
|
||||
await stdSibling(resp, panel, series, {})(next)([]);
|
||||
expect(next.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('calls next when std. deviation bands set', () => {
|
||||
test('calls next when std. deviation bands set', async () => {
|
||||
series.metrics[1].mode = 'band';
|
||||
const next = jest.fn((results) => results);
|
||||
const results = stdSibling(resp, panel, series)(next)([]);
|
||||
const results = await stdSibling(resp, panel, series, {})(next)([]);
|
||||
expect(next.mock.calls.length).toEqual(1);
|
||||
expect(results).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('creates a series', () => {
|
||||
test('creates a series', async () => {
|
||||
const next = (results) => results;
|
||||
const results = stdSibling(resp, panel, series)(next)([]);
|
||||
const results = await stdSibling(resp, panel, series, {})(next)([]);
|
||||
expect(results).toHaveLength(1);
|
||||
|
||||
expect(results[0]).toEqual({
|
||||
|
|
|
@ -60,15 +60,17 @@ describe('timeShift(resp, panel, series)', () => {
|
|||
};
|
||||
});
|
||||
|
||||
test('calls next when finished', () => {
|
||||
test('calls next when finished', async () => {
|
||||
const next = jest.fn();
|
||||
timeShift(resp, panel, series)(next)([]);
|
||||
await timeShift(resp, panel, series, {})(next)([]);
|
||||
|
||||
expect(next.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('creates a series', () => {
|
||||
const next = timeShift(resp, panel, series)((results) => results);
|
||||
const results = stdMetric(resp, panel, series)(next)([]);
|
||||
test('creates a series', async () => {
|
||||
const next = await timeShift(resp, panel, series, {})((results) => results);
|
||||
const results = await stdMetric(resp, panel, series, {})(next)([]);
|
||||
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0]).toHaveProperty('color', 'rgb(255, 0, 0)');
|
||||
expect(results[0]).toHaveProperty('id', 'test');
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
// import percentile from './percentile';
|
||||
import { stdMetric } from './std_metric';
|
||||
import { stdSibling } from './std_sibling';
|
||||
import { seriesAgg } from './series_agg';
|
||||
|
|
|
@ -22,8 +22,8 @@ import { getLastMetric } from '../../helpers/get_last_metric';
|
|||
import { toPercentileNumber } from '../../../../../common/to_percentile_number';
|
||||
import { METRIC_TYPES } from '../../../../../common/metric_types';
|
||||
|
||||
export function percentile(bucket, panel, series) {
|
||||
return (next) => (results) => {
|
||||
export function percentile(bucket, panel, series, meta, extractFields) {
|
||||
return (next) => async (results) => {
|
||||
const metric = getLastMetric(series);
|
||||
|
||||
if (metric.type !== METRIC_TYPES.PERCENTILE) {
|
||||
|
@ -34,7 +34,7 @@ export function percentile(bucket, panel, series) {
|
|||
aggregations: bucket,
|
||||
};
|
||||
|
||||
getSplits(fakeResp, panel, series).forEach((split) => {
|
||||
(await getSplits(fakeResp, panel, series, meta, extractFields)).forEach((split) => {
|
||||
// table allows only one percentile in a series (the last one will be chosen in case of several)
|
||||
const percentile = last(metric.percentiles);
|
||||
const percentileKey = toPercentileNumber(percentile.value);
|
||||
|
@ -45,6 +45,7 @@ export function percentile(bucket, panel, series) {
|
|||
|
||||
results.push({
|
||||
id: split.id,
|
||||
label: `${split.label} (${percentile.value ?? 0})`,
|
||||
data,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,8 +23,8 @@ import { toPercentileNumber } from '../../../../../common/to_percentile_number';
|
|||
import { getAggValue } from '../../helpers/get_agg_value';
|
||||
import { METRIC_TYPES } from '../../../../../common/metric_types';
|
||||
|
||||
export function percentileRank(bucket, panel, series) {
|
||||
return (next) => (results) => {
|
||||
export function percentileRank(bucket, panel, series, meta, extractFields) {
|
||||
return (next) => async (results) => {
|
||||
const metric = getLastMetric(series);
|
||||
|
||||
if (metric.type !== METRIC_TYPES.PERCENTILE_RANK) {
|
||||
|
@ -35,7 +35,7 @@ export function percentileRank(bucket, panel, series) {
|
|||
aggregations: bucket,
|
||||
};
|
||||
|
||||
getSplits(fakeResp, panel, series).forEach((split) => {
|
||||
(await getSplits(fakeResp, panel, series, meta, extractFields)).forEach((split) => {
|
||||
// table allows only one percentile rank in a series (the last one will be chosen in case of several)
|
||||
const lastRankValue = last(metric.values);
|
||||
const percentileRank = toPercentileNumber(lastRankValue);
|
||||
|
@ -51,7 +51,7 @@ export function percentileRank(bucket, panel, series) {
|
|||
results.push({
|
||||
data,
|
||||
id: split.id,
|
||||
label: `${split.label} (${percentileRank || 0})`,
|
||||
label: `${split.label} (${lastRankValue ?? 0})`,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -21,8 +21,8 @@ import { SeriesAgg } from './_series_agg';
|
|||
import _ from 'lodash';
|
||||
import { calculateLabel } from '../../../../../common/calculate_label';
|
||||
|
||||
export function seriesAgg(resp, panel, series) {
|
||||
return (next) => (results) => {
|
||||
export function seriesAgg(resp, panel, series, meta, extractFields) {
|
||||
return (next) => async (results) => {
|
||||
if (series.aggregate_by && series.aggregate_function) {
|
||||
const targetSeries = [];
|
||||
// Filter out the seires with the matching metric and store them
|
||||
|
@ -36,9 +36,14 @@ export function seriesAgg(resp, panel, series) {
|
|||
});
|
||||
const fn = SeriesAgg[series.aggregate_function];
|
||||
const data = fn(targetSeries);
|
||||
|
||||
const fieldsForMetaIndex = meta.index ? await extractFields(meta.index) : [];
|
||||
|
||||
results.push({
|
||||
id: `${series.id}`,
|
||||
label: series.label || calculateLabel(_.last(series.metrics), series.metrics),
|
||||
label:
|
||||
series.label ||
|
||||
calculateLabel(_.last(series.metrics), series.metrics, fieldsForMetaIndex),
|
||||
data: _.first(data),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@ import { getLastMetric } from '../../helpers/get_last_metric';
|
|||
import { mapBucket } from '../../helpers/map_bucket';
|
||||
import { METRIC_TYPES } from '../../../../../common/metric_types';
|
||||
|
||||
export function stdMetric(bucket, panel, series) {
|
||||
return (next) => (results) => {
|
||||
export function stdMetric(bucket, panel, series, meta, extractFields) {
|
||||
return (next) => async (results) => {
|
||||
const metric = getLastMetric(series);
|
||||
|
||||
if (metric.type === METRIC_TYPES.STD_DEVIATION && metric.mode === 'band') {
|
||||
|
@ -42,7 +42,7 @@ export function stdMetric(bucket, panel, series) {
|
|||
aggregations: bucket,
|
||||
};
|
||||
|
||||
getSplits(fakeResp, panel, series).forEach((split) => {
|
||||
(await getSplits(fakeResp, panel, series, meta, extractFields)).forEach((split) => {
|
||||
const data = split.timeseries.buckets.map(mapBucket(metric));
|
||||
results.push({
|
||||
id: split.id,
|
||||
|
|
|
@ -21,15 +21,15 @@ import { getSplits } from '../../helpers/get_splits';
|
|||
import { getLastMetric } from '../../helpers/get_last_metric';
|
||||
import { getSiblingAggValue } from '../../helpers/get_sibling_agg_value';
|
||||
|
||||
export function stdSibling(bucket, panel, series) {
|
||||
return (next) => (results) => {
|
||||
export function stdSibling(bucket, panel, series, meta, extractFields) {
|
||||
return (next) => async (results) => {
|
||||
const metric = getLastMetric(series);
|
||||
|
||||
if (!/_bucket$/.test(metric.type)) return next(results);
|
||||
if (metric.type === 'std_deviation_bucket' && metric.mode === 'band') return next(results);
|
||||
|
||||
const fakeResp = { aggregations: bucket };
|
||||
getSplits(fakeResp, panel, series).forEach((split) => {
|
||||
(await getSplits(fakeResp, panel, series, meta, extractFields)).forEach((split) => {
|
||||
const data = split.timeseries.buckets.map((b) => {
|
||||
return [b.key, getSiblingAggValue(split, metric)];
|
||||
});
|
||||
|
|
|
@ -78,7 +78,7 @@ const body = JSON.parse(`
|
|||
`);
|
||||
|
||||
describe('buildRequestBody(req)', () => {
|
||||
test('returns a valid body', () => {
|
||||
test('returns a valid body', async () => {
|
||||
const panel = body.panels[0];
|
||||
const series = panel.series[0];
|
||||
const getValidTimeInterval = jest.fn(() => '10s');
|
||||
|
@ -91,14 +91,16 @@ describe('buildRequestBody(req)', () => {
|
|||
queryStringOptions: {},
|
||||
};
|
||||
const indexPatternObject = {};
|
||||
const doc = buildRequestBody(
|
||||
const doc = await buildRequestBody(
|
||||
{ payload: body },
|
||||
panel,
|
||||
series,
|
||||
config,
|
||||
indexPatternObject,
|
||||
capabilities,
|
||||
{ barTargetUiSettings: 50 }
|
||||
{
|
||||
get: async () => 50,
|
||||
}
|
||||
);
|
||||
|
||||
expect(doc).toEqual({
|
||||
|
|
|
@ -34,8 +34,8 @@ import { processors } from '../request_processors/series/index';
|
|||
* ]
|
||||
* @returns {Object} doc - processed body
|
||||
*/
|
||||
export function buildRequestBody(...args: any[]) {
|
||||
export async function buildRequestBody(...args: any[]) {
|
||||
const processor = buildProcessorFunction(processors, ...args);
|
||||
const doc = processor({});
|
||||
const doc = await processor({});
|
||||
return doc;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue