Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Wylie Conlon <wylieconlon@gmail.com> Co-authored-by: Michael Marcialis <michael.marcialis@elastic.co> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Wylie Conlon <wylieconlon@gmail.com> Co-authored-by: Michael Marcialis <michael.marcialis@elastic.co>
This commit is contained in:
parent
97aef867ba
commit
87ee412c01
17 changed files with 578 additions and 92 deletions
|
@ -35,6 +35,17 @@ search: {
|
|||
siblingPipelineType: string;
|
||||
termsAggFilter: string[];
|
||||
toAbsoluteDates: typeof toAbsoluteDates;
|
||||
boundsDescendingRaw: ({
|
||||
bound: number;
|
||||
interval: import("moment").Duration;
|
||||
boundLabel: string;
|
||||
intervalLabel: string;
|
||||
} | {
|
||||
bound: import("moment").Duration;
|
||||
interval: import("moment").Duration;
|
||||
boundLabel: string;
|
||||
intervalLabel: string;
|
||||
})[];
|
||||
};
|
||||
getRequestInspectorStats: typeof getRequestInspectorStats;
|
||||
getResponseInspectorStats: typeof getResponseInspectorStats;
|
||||
|
|
|
@ -6,75 +6,205 @@
|
|||
* Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment';
|
||||
|
||||
const boundsDescending = [
|
||||
export const boundsDescendingRaw = [
|
||||
{
|
||||
bound: Infinity,
|
||||
interval: Number(moment.duration(1, 'year')),
|
||||
interval: moment.duration(1, 'year'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.infinityLabel', {
|
||||
defaultMessage: 'More than a year',
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.yearLabel', {
|
||||
defaultMessage: 'a year',
|
||||
}),
|
||||
},
|
||||
{
|
||||
bound: Number(moment.duration(1, 'year')),
|
||||
interval: Number(moment.duration(1, 'month')),
|
||||
bound: moment.duration(1, 'year'),
|
||||
interval: moment.duration(1, 'month'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.yearLabel', {
|
||||
defaultMessage: 'a year',
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.monthLabel', {
|
||||
defaultMessage: 'a month',
|
||||
}),
|
||||
},
|
||||
{
|
||||
bound: Number(moment.duration(3, 'week')),
|
||||
interval: Number(moment.duration(1, 'week')),
|
||||
bound: moment.duration(3, 'week'),
|
||||
interval: moment.duration(1, 'week'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.dayLabel', {
|
||||
defaultMessage: '{amount, plural, one {a day} other {# days}}',
|
||||
values: { amount: 21 },
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.dayLabel', {
|
||||
defaultMessage: '{amount, plural, one {a day} other {# days}}',
|
||||
values: { amount: 7 },
|
||||
}),
|
||||
},
|
||||
{
|
||||
bound: Number(moment.duration(1, 'week')),
|
||||
interval: Number(moment.duration(1, 'd')),
|
||||
bound: moment.duration(1, 'week'),
|
||||
interval: moment.duration(1, 'd'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.dayLabel', {
|
||||
defaultMessage: '{amount, plural, one {a day} other {# days}}',
|
||||
values: { amount: 7 },
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.dayLabel', {
|
||||
defaultMessage: '{amount, plural, one {a day} other {# days}}',
|
||||
values: { amount: 1 },
|
||||
}),
|
||||
},
|
||||
{
|
||||
bound: Number(moment.duration(24, 'hour')),
|
||||
interval: Number(moment.duration(12, 'hour')),
|
||||
bound: moment.duration(24, 'hour'),
|
||||
interval: moment.duration(12, 'hour'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.dayLabel', {
|
||||
defaultMessage: '{amount, plural, one {a day} other {# days}}',
|
||||
values: { amount: 1 },
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.hourLabel', {
|
||||
defaultMessage: '{amount, plural, one {an hour} other {# hours}}',
|
||||
values: { amount: 12 },
|
||||
}),
|
||||
},
|
||||
{
|
||||
bound: Number(moment.duration(6, 'hour')),
|
||||
interval: Number(moment.duration(3, 'hour')),
|
||||
bound: moment.duration(6, 'hour'),
|
||||
interval: moment.duration(3, 'hour'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.hourLabel', {
|
||||
defaultMessage: '{amount, plural, one {an hour} other {# hours}}',
|
||||
values: { amount: 6 },
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.hourLabel', {
|
||||
defaultMessage: '{amount, plural, one {an hour} other {# hours}}',
|
||||
values: { amount: 3 },
|
||||
}),
|
||||
},
|
||||
{
|
||||
bound: Number(moment.duration(2, 'hour')),
|
||||
interval: Number(moment.duration(1, 'hour')),
|
||||
bound: moment.duration(2, 'hour'),
|
||||
interval: moment.duration(1, 'hour'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.hourLabel', {
|
||||
defaultMessage: '{amount, plural, one {an hour} other {# hours}}',
|
||||
values: { amount: 2 },
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.hourLabel', {
|
||||
defaultMessage: '{amount, plural, one {an hour} other {# hours}}',
|
||||
values: { amount: 1 },
|
||||
}),
|
||||
},
|
||||
{
|
||||
bound: Number(moment.duration(45, 'minute')),
|
||||
interval: Number(moment.duration(30, 'minute')),
|
||||
bound: moment.duration(45, 'minute'),
|
||||
interval: moment.duration(30, 'minute'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.minuteLabel', {
|
||||
defaultMessage: '{amount, plural, one {a minute} other {# minutes}}',
|
||||
values: { amount: 45 },
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.minuteLabel', {
|
||||
defaultMessage: '{amount, plural, one {a minute} other {# minutes}}',
|
||||
values: { amount: 30 },
|
||||
}),
|
||||
},
|
||||
{
|
||||
bound: Number(moment.duration(20, 'minute')),
|
||||
interval: Number(moment.duration(10, 'minute')),
|
||||
bound: moment.duration(20, 'minute'),
|
||||
interval: moment.duration(10, 'minute'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.minuteLabel', {
|
||||
defaultMessage: '{amount, plural, one {a minute} other {# minutes}}',
|
||||
values: { amount: 20 },
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.minuteLabel', {
|
||||
defaultMessage: '{amount, plural, one {a minute} other {# minutes}}',
|
||||
values: { amount: 10 },
|
||||
}),
|
||||
},
|
||||
{
|
||||
bound: Number(moment.duration(9, 'minute')),
|
||||
interval: Number(moment.duration(5, 'minute')),
|
||||
bound: moment.duration(9, 'minute'),
|
||||
interval: moment.duration(5, 'minute'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.minuteLabel', {
|
||||
defaultMessage: '{amount, plural, one {a minute} other {# minutes}}',
|
||||
values: { amount: 9 },
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.minuteLabel', {
|
||||
defaultMessage: '{amount, plural, one {a minute} other {# minutes}}',
|
||||
values: { amount: 5 },
|
||||
}),
|
||||
},
|
||||
{
|
||||
bound: Number(moment.duration(3, 'minute')),
|
||||
interval: Number(moment.duration(1, 'minute')),
|
||||
bound: moment.duration(3, 'minute'),
|
||||
interval: moment.duration(1, 'minute'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.minuteLabel', {
|
||||
defaultMessage: '{amount, plural, one {a minute} other {# minutes}}',
|
||||
values: { amount: 3 },
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.minuteLabel', {
|
||||
defaultMessage: '{amount, plural, one {a minute} other {# minutes}}',
|
||||
values: { amount: 1 },
|
||||
}),
|
||||
},
|
||||
{
|
||||
bound: Number(moment.duration(45, 'second')),
|
||||
interval: Number(moment.duration(30, 'second')),
|
||||
bound: moment.duration(45, 'second'),
|
||||
interval: moment.duration(30, 'second'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.secondLabel', {
|
||||
defaultMessage: '{amount, plural, one {a second} other {# seconds}}',
|
||||
values: { amount: 45 },
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.secondLabel', {
|
||||
defaultMessage: '{amount, plural, one {a second} other {# seconds}}',
|
||||
values: { amount: 30 },
|
||||
}),
|
||||
},
|
||||
{
|
||||
bound: Number(moment.duration(15, 'second')),
|
||||
interval: Number(moment.duration(10, 'second')),
|
||||
bound: moment.duration(15, 'second'),
|
||||
interval: moment.duration(10, 'second'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.secondLabel', {
|
||||
defaultMessage: '{amount, plural, one {a second} other {# seconds}}',
|
||||
values: { amount: 15 },
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.secondLabel', {
|
||||
defaultMessage: '{amount, plural, one {a second} other {# seconds}}',
|
||||
values: { amount: 10 },
|
||||
}),
|
||||
},
|
||||
{
|
||||
bound: Number(moment.duration(7.5, 'second')),
|
||||
interval: Number(moment.duration(5, 'second')),
|
||||
bound: moment.duration(7.5, 'second'),
|
||||
interval: moment.duration(5, 'second'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.secondLabel', {
|
||||
defaultMessage: '{amount, plural, one {a second} other {# seconds}}',
|
||||
values: { amount: 7.5 },
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.secondLabel', {
|
||||
defaultMessage: '{amount, plural, one {a second} other {# seconds}}',
|
||||
values: { amount: 5 },
|
||||
}),
|
||||
},
|
||||
{
|
||||
bound: Number(moment.duration(5, 'second')),
|
||||
interval: Number(moment.duration(1, 'second')),
|
||||
bound: moment.duration(5, 'second'),
|
||||
interval: moment.duration(1, 'second'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.secondLabel', {
|
||||
defaultMessage: '{amount, plural, one {a second} other {# seconds}}',
|
||||
values: { amount: 5 },
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.secondLabel', {
|
||||
defaultMessage: '{amount, plural, one {a second} other {# seconds}}',
|
||||
values: { amount: 1 },
|
||||
}),
|
||||
},
|
||||
{
|
||||
bound: Number(moment.duration(500, 'ms')),
|
||||
interval: Number(moment.duration(100, 'ms')),
|
||||
bound: moment.duration(500, 'ms'),
|
||||
interval: moment.duration(100, 'ms'),
|
||||
boundLabel: i18n.translate('data.search.timeBuckets.millisecondLabel', {
|
||||
defaultMessage: '{amount, plural, one {a millisecond} other {# milliseconds}}',
|
||||
values: { amount: 500 },
|
||||
}),
|
||||
intervalLabel: i18n.translate('data.search.timeBuckets.millisecondLabel', {
|
||||
defaultMessage: '{amount, plural, one {a millisecond} other {# milliseconds}}',
|
||||
values: { amount: 100 },
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const boundsDescending = boundsDescendingRaw.map(({ bound, interval }) => ({
|
||||
bound: Number(bound),
|
||||
interval: Number(interval),
|
||||
}));
|
||||
|
||||
function getPerBucketMs(count: number, duration: number) {
|
||||
const ms = duration / count;
|
||||
return isFinite(ms) ? ms : NaN;
|
||||
|
|
|
@ -307,6 +307,7 @@ import {
|
|||
parseEsInterval,
|
||||
parseInterval,
|
||||
toAbsoluteDates,
|
||||
boundsDescendingRaw,
|
||||
// expressions utils
|
||||
getRequestInspectorStats,
|
||||
getResponseInspectorStats,
|
||||
|
@ -416,6 +417,7 @@ export const search = {
|
|||
siblingPipelineType,
|
||||
termsAggFilter,
|
||||
toAbsoluteDates,
|
||||
boundsDescendingRaw,
|
||||
},
|
||||
getRequestInspectorStats,
|
||||
getResponseInspectorStats,
|
||||
|
|
|
@ -2213,6 +2213,17 @@ export const search: {
|
|||
siblingPipelineType: string;
|
||||
termsAggFilter: string[];
|
||||
toAbsoluteDates: typeof toAbsoluteDates;
|
||||
boundsDescendingRaw: ({
|
||||
bound: number;
|
||||
interval: import("moment").Duration;
|
||||
boundLabel: string;
|
||||
intervalLabel: string;
|
||||
} | {
|
||||
bound: import("moment").Duration;
|
||||
interval: import("moment").Duration;
|
||||
boundLabel: string;
|
||||
intervalLabel: string;
|
||||
})[];
|
||||
};
|
||||
getRequestInspectorStats: typeof getRequestInspectorStats;
|
||||
getResponseInspectorStats: typeof getResponseInspectorStats;
|
||||
|
@ -2616,21 +2627,21 @@ export const UI_SETTINGS: {
|
|||
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:417:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:418:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:421:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:425:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:418:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:419:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:426:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:34:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/search/session/session_service.ts:41:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts
|
||||
|
||||
|
|
|
@ -299,6 +299,17 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
}
|
||||
);
|
||||
|
||||
// Need to workout early on the error to decide whether to show this or an help text
|
||||
const fieldErrorMessage =
|
||||
(selectedOperationDefinition?.input !== 'fullReference' ||
|
||||
(incompleteOperation && operationDefinitionMap[incompleteOperation].input === 'field')) &&
|
||||
getErrorMessage(
|
||||
selectedColumn,
|
||||
Boolean(incompleteOperation),
|
||||
selectedOperationDefinition?.input,
|
||||
currentFieldIsInvalid
|
||||
);
|
||||
|
||||
return (
|
||||
<div id={columnId}>
|
||||
<div className="lnsIndexPatternDimensionEditor__section lnsIndexPatternDimensionEditor__section--shaded">
|
||||
|
@ -342,6 +353,11 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
existingFields={state.existingFields}
|
||||
selectionStyle={selectedOperationDefinition.selectionStyle}
|
||||
dateRange={dateRange}
|
||||
labelAppend={selectedOperationDefinition?.getHelpMessage?.({
|
||||
data: props.data,
|
||||
uiSettings: props.uiSettings,
|
||||
currentColumn: state.layers[layerId].columns[columnId],
|
||||
})}
|
||||
{...services}
|
||||
/>
|
||||
);
|
||||
|
@ -360,12 +376,15 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
})}
|
||||
fullWidth
|
||||
isInvalid={Boolean(incompleteOperation || currentFieldIsInvalid)}
|
||||
error={getErrorMessage(
|
||||
selectedColumn,
|
||||
Boolean(incompleteOperation),
|
||||
selectedOperationDefinition?.input,
|
||||
currentFieldIsInvalid
|
||||
)}
|
||||
error={fieldErrorMessage}
|
||||
labelAppend={
|
||||
!fieldErrorMessage &&
|
||||
selectedOperationDefinition?.getHelpMessage?.({
|
||||
data: props.data,
|
||||
uiSettings: props.uiSettings,
|
||||
currentColumn: state.layers[layerId].columns[columnId],
|
||||
})
|
||||
}
|
||||
>
|
||||
<FieldSelect
|
||||
fieldIsInvalid={currentFieldIsInvalid}
|
||||
|
|
|
@ -187,6 +187,9 @@ describe('IndexPatternDimensionEditorPanel', () => {
|
|||
id: 'bytes',
|
||||
title: 'Bytes',
|
||||
}),
|
||||
deserialize: jest.fn().mockReturnValue({
|
||||
convert: () => 'formatted',
|
||||
}),
|
||||
} as unknown) as DataPublicPluginStart['fieldFormats'],
|
||||
} as unknown) as DataPublicPluginStart,
|
||||
core: {} as CoreSetup,
|
||||
|
|
|
@ -8,7 +8,13 @@ import './dimension_editor.scss';
|
|||
import _ from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFormRow, EuiSpacer, EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
import {
|
||||
EuiFormRow,
|
||||
EuiFormRowProps,
|
||||
EuiSpacer,
|
||||
EuiComboBox,
|
||||
EuiComboBoxOptionOption,
|
||||
} from '@elastic/eui';
|
||||
import type { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public';
|
||||
import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
|
||||
import type { DataPublicPluginStart } from 'src/plugins/data/public';
|
||||
|
@ -40,6 +46,7 @@ export interface ReferenceEditorProps {
|
|||
currentIndexPattern: IndexPattern;
|
||||
existingFields: IndexPatternPrivateState['existingFields'];
|
||||
dateRange: DateRange;
|
||||
labelAppend?: EuiFormRowProps['labelAppend'];
|
||||
|
||||
// Services
|
||||
uiSettings: IUiSettingsClient;
|
||||
|
@ -59,6 +66,7 @@ export function ReferenceEditor(props: ReferenceEditorProps) {
|
|||
validation,
|
||||
selectionStyle,
|
||||
dateRange,
|
||||
labelAppend,
|
||||
...services
|
||||
} = props;
|
||||
|
||||
|
@ -251,6 +259,7 @@ export function ReferenceEditor(props: ReferenceEditorProps) {
|
|||
})}
|
||||
fullWidth
|
||||
isInvalid={showFieldInvalid}
|
||||
labelAppend={labelAppend}
|
||||
>
|
||||
<FieldSelect
|
||||
fieldIsInvalid={showFieldInvalid}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
.lnsHelpPopover__panel {
|
||||
max-width: $euiSize * 30;
|
||||
}
|
||||
|
||||
.lnsHelpPopover__content {
|
||||
@include euiYScrollWithShadows;
|
||||
max-height: 40vh;
|
||||
padding: $euiSizeM;
|
||||
}
|
||||
|
||||
.lnsHelpPopover__buttonIcon {
|
||||
margin-right: $euiSizeXS;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import {
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiLinkButtonProps,
|
||||
EuiPopover,
|
||||
EuiPopoverProps,
|
||||
EuiPopoverTitle,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import './help_popover.scss';
|
||||
|
||||
export const HelpPopoverButton = ({
|
||||
children,
|
||||
onClick,
|
||||
}: {
|
||||
children: string;
|
||||
onClick: EuiLinkButtonProps['onClick'];
|
||||
}) => {
|
||||
return (
|
||||
<EuiText size="xs">
|
||||
<EuiLink onClick={onClick}>
|
||||
<EuiIcon className="lnsHelpPopover__buttonIcon" size="s" type="help" />
|
||||
|
||||
{children}
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
);
|
||||
};
|
||||
|
||||
export const HelpPopover = ({
|
||||
anchorPosition,
|
||||
button,
|
||||
children,
|
||||
closePopover,
|
||||
isOpen,
|
||||
title,
|
||||
}: {
|
||||
anchorPosition?: EuiPopoverProps['anchorPosition'];
|
||||
button: EuiPopoverProps['button'];
|
||||
children: ReactNode;
|
||||
closePopover: EuiPopoverProps['closePopover'];
|
||||
isOpen: EuiPopoverProps['isOpen'];
|
||||
title?: string;
|
||||
}) => {
|
||||
return (
|
||||
<EuiPopover
|
||||
anchorPosition={anchorPosition}
|
||||
button={button}
|
||||
className="lnsHelpPopover"
|
||||
closePopover={closePopover}
|
||||
isOpen={isOpen}
|
||||
ownFocus
|
||||
panelClassName="lnsHelpPopover__panel"
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
{title && <EuiPopoverTitle>{title}</EuiPopoverTitle>}
|
||||
|
||||
<EuiText className="lnsHelpPopover__content" size="s">
|
||||
{children}
|
||||
</EuiText>
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
|
@ -5,10 +5,9 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { EuiFormRow } from '@elastic/eui';
|
||||
import { EuiFieldNumber } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { useState } from 'react';
|
||||
import { EuiFieldNumber, EuiFormRow } from '@elastic/eui';
|
||||
import { FormattedIndexPatternColumn, ReferenceBasedIndexPatternColumn } from '../column_types';
|
||||
import { IndexPatternLayer } from '../../../types';
|
||||
import {
|
||||
|
@ -21,6 +20,7 @@ import {
|
|||
import { updateColumnParam } from '../../layer_helpers';
|
||||
import { isValidNumber, useDebounceWithOptions } from '../helpers';
|
||||
import { adjustTimeScaleOnOtherColumnChange } from '../../time_scale_utils';
|
||||
import { HelpPopover, HelpPopoverButton } from '../../../help_popover';
|
||||
import type { OperationDefinition, ParamEditorProps } from '..';
|
||||
|
||||
const ofName = buildLabelFunction((name?: string) => {
|
||||
|
@ -111,6 +111,7 @@ export const movingAverageOperation: OperationDefinition<
|
|||
})
|
||||
);
|
||||
},
|
||||
getHelpMessage: () => <MovingAveragePopup />,
|
||||
getDisabledStatus(indexPattern, layer) {
|
||||
return checkForDateHistogram(
|
||||
layer,
|
||||
|
@ -168,3 +169,79 @@ function MovingAverageParamEditor({
|
|||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
||||
const MovingAveragePopup = () => {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
return (
|
||||
<HelpPopover
|
||||
anchorPosition="upCenter"
|
||||
button={
|
||||
<HelpPopoverButton onClick={() => setIsPopoverOpen(!isPopoverOpen)}>
|
||||
{i18n.translate('xpack.lens.indexPattern.movingAverage.helpText', {
|
||||
defaultMessage: 'How it works',
|
||||
})}
|
||||
</HelpPopoverButton>
|
||||
}
|
||||
closePopover={() => setIsPopoverOpen(false)}
|
||||
isOpen={isPopoverOpen}
|
||||
title={i18n.translate('xpack.lens.indexPattern.movingAverage.titleHelp', {
|
||||
defaultMessage: 'How moving average works',
|
||||
})}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.indexPattern.movingAverage.basicExplanation"
|
||||
defaultMessage="Moving average slides a window across the data and displays the average value in the window."
|
||||
/>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.indexPattern.movingAverage.longerExplanation"
|
||||
defaultMessage="To calculate the moving average, Lens uses the mean of the window and applies a skip policy for gaps.
|
||||
For missing values, the bucket is skipped and the calculation is performed on the next value."
|
||||
/>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.indexPattern.movingAverage.tableExplanation"
|
||||
defaultMessage="For example, given the data [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], we can calculate a simple moving average with a window size of 5 as follows:"
|
||||
/>
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>(1 + 2 + 3 + 4 + 5) / 5 = 3</li>
|
||||
<li>(2 + 3 + 4 + 5 + 6) / 5 = 4</li>
|
||||
<li>...</li>
|
||||
<li>(5 + 6 + 7 + 8 + 9) / 5 = 7</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.indexPattern.movingAverage.windowLimitations"
|
||||
defaultMessage="The window does not include the current value."
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.indexPattern.movingAverage.windowInitialPartial"
|
||||
defaultMessage="For the initial part of the series the window is partial, until it reaches the requested number of items. For instance with a window size of 5:"
|
||||
/>
|
||||
</p>
|
||||
<ul>
|
||||
<li>(1 + 2) / 2 = 1.5</li>
|
||||
<li>(1 + 2 + 3) / 3 = 2</li>
|
||||
<li>(1 + 2 + 3 + 4) / 4 = 2.5</li>
|
||||
<li>(1 + 2 + 3 + 4 + 5) / 5 = 3</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.indexPattern.movingAverage.limitations"
|
||||
defaultMessage="Note the first moving average value start from the second item onward. The moving average is supported only for date histograms."
|
||||
/>
|
||||
</p>
|
||||
</HelpPopover>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,31 +4,36 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import {
|
||||
EuiBasicTable,
|
||||
EuiCode,
|
||||
EuiFieldNumber,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiSelect,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiSwitchEvent,
|
||||
EuiFieldNumber,
|
||||
EuiSelect,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiTextColor,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { updateColumnParam } from '../layer_helpers';
|
||||
import { OperationDefinition } from './index';
|
||||
import { FieldBasedIndexPatternColumn } from './column_types';
|
||||
import {
|
||||
AggFunctionsMapping,
|
||||
DataPublicPluginStart,
|
||||
IndexPatternAggRestrictions,
|
||||
search,
|
||||
UI_SETTINGS,
|
||||
} from '../../../../../../../src/plugins/data/public';
|
||||
import { buildExpressionFunction } from '../../../../../../../src/plugins/expressions/public';
|
||||
import { getInvalidFieldMessage, getSafeName } from './helpers';
|
||||
import { HelpPopover, HelpPopoverButton } from '../../help_popover';
|
||||
|
||||
const { isValidInterval } = search.aggs;
|
||||
const autoInterval = 'auto';
|
||||
|
@ -54,6 +59,7 @@ export const dateHistogramOperation: OperationDefinition<
|
|||
priority: 5, // Highest priority level used
|
||||
getErrorMessage: (layer, columnId, indexPattern) =>
|
||||
getInvalidFieldMessage(layer.columns[columnId] as FieldBasedIndexPatternColumn, indexPattern),
|
||||
getHelpMessage: (props) => <AutoDateHistogramPopover {...props} />,
|
||||
getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => {
|
||||
if (
|
||||
type === 'date' &&
|
||||
|
@ -334,3 +340,77 @@ function restrictedInterval(aggregationRestrictions?: Partial<IndexPatternAggRes
|
|||
aggregationRestrictions.date_histogram.fixed_interval
|
||||
);
|
||||
}
|
||||
|
||||
const AutoDateHistogramPopover = ({ data }: { data: DataPublicPluginStart }) => {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const infiniteBound = i18n.translate('xpack.lens.indexPattern.dateHistogram.moreThanYear', {
|
||||
defaultMessage: 'More than a year',
|
||||
});
|
||||
const upToLabel = i18n.translate('xpack.lens.indexPattern.dateHistogram.upTo', {
|
||||
defaultMessage: 'Up to',
|
||||
});
|
||||
|
||||
return (
|
||||
<HelpPopover
|
||||
anchorPosition="upCenter"
|
||||
button={
|
||||
<HelpPopoverButton onClick={() => setIsPopoverOpen(!isPopoverOpen)}>
|
||||
{i18n.translate('xpack.lens.indexPattern.dateHistogram.autoHelpText', {
|
||||
defaultMessage: 'How it works',
|
||||
})}
|
||||
</HelpPopoverButton>
|
||||
}
|
||||
closePopover={() => setIsPopoverOpen(false)}
|
||||
isOpen={isPopoverOpen}
|
||||
title={i18n.translate('xpack.lens.indexPattern.dateHistogram.titleHelp', {
|
||||
defaultMessage: 'How auto date histogram works',
|
||||
})}
|
||||
>
|
||||
<p>
|
||||
{i18n.translate('xpack.lens.indexPattern.dateHistogram.autoBasicExplanation', {
|
||||
defaultMessage: 'The auto date histogram splits a date field into buckets by interval.',
|
||||
})}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.indexPattern.dateHistogram.autoLongerExplanation"
|
||||
defaultMessage="Lens automatically chooses an interval for you by dividing the specified time range by the
|
||||
{targetBarSetting} advanced setting. The calculation tries to present “nice” time interval buckets. The maximum
|
||||
number of bars is set by the {maxBarSetting} value."
|
||||
values={{
|
||||
maxBarSetting: <EuiCode>{UI_SETTINGS.HISTOGRAM_MAX_BARS}</EuiCode>,
|
||||
targetBarSetting: <EuiCode>{UI_SETTINGS.HISTOGRAM_BAR_TARGET}</EuiCode>,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{i18n.translate('xpack.lens.indexPattern.dateHistogram.autoAdvancedExplanation', {
|
||||
defaultMessage: 'The interval follows this logic:',
|
||||
})}
|
||||
</p>
|
||||
|
||||
<EuiBasicTable
|
||||
items={search.aggs.boundsDescendingRaw.map(({ bound, boundLabel, intervalLabel }) => ({
|
||||
bound: typeof bound === 'number' ? infiniteBound : `${upToLabel} ${boundLabel}`,
|
||||
interval: intervalLabel,
|
||||
}))}
|
||||
columns={[
|
||||
{
|
||||
field: 'bound',
|
||||
name: i18n.translate('xpack.lens.indexPattern.dateHistogram.autoBoundHeader', {
|
||||
defaultMessage: 'Target interval measured',
|
||||
}),
|
||||
},
|
||||
{
|
||||
field: 'interval',
|
||||
name: i18n.translate('xpack.lens.indexPattern.dateHistogram.autoIntervalHeader', {
|
||||
defaultMessage: 'Interval used',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</HelpPopover>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -126,6 +126,12 @@ export interface ParamEditorProps<C> {
|
|||
data: DataPublicPluginStart;
|
||||
}
|
||||
|
||||
export interface HelpProps<C> {
|
||||
currentColumn: C;
|
||||
uiSettings: IUiSettingsClient;
|
||||
data: DataPublicPluginStart;
|
||||
}
|
||||
|
||||
export type TimeScalingMode = 'disabled' | 'mandatory' | 'optional';
|
||||
|
||||
interface BaseOperationDefinitionProps<C extends BaseIndexPatternColumn> {
|
||||
|
@ -201,6 +207,8 @@ interface BaseOperationDefinitionProps<C extends BaseIndexPatternColumn> {
|
|||
* If set to optional, time scaling won't be enabled by default and can be removed.
|
||||
*/
|
||||
timeScalingMode?: TimeScalingMode;
|
||||
|
||||
getHelpMessage?: (props: HelpProps<C>) => React.ReactNode;
|
||||
}
|
||||
|
||||
interface BaseBuildColumnArgs {
|
||||
|
|
|
@ -6,21 +6,73 @@
|
|||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiButtonIcon,
|
||||
EuiCode,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiRange,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiButtonIcon,
|
||||
EuiToolTip,
|
||||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import { IFieldFormat } from 'src/plugins/data/public';
|
||||
import type { IFieldFormat } from 'src/plugins/data/public';
|
||||
import { UI_SETTINGS } from '../../../../../../../../src/plugins/data/public';
|
||||
import { RangeColumnParams, UpdateParamsFnType, MODES_TYPES } from './ranges';
|
||||
import { AdvancedRangeEditor } from './advanced_editor';
|
||||
import { TYPING_DEBOUNCE_TIME, MODES, MIN_HISTOGRAM_BARS } from './constants';
|
||||
import { useDebounceWithOptions } from '../helpers';
|
||||
import { HelpPopover, HelpPopoverButton } from '../../../help_popover';
|
||||
|
||||
const GranularityHelpPopover = () => {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<HelpPopover
|
||||
anchorPosition="upCenter"
|
||||
button={
|
||||
<HelpPopoverButton onClick={() => setIsPopoverOpen(!isPopoverOpen)}>
|
||||
{i18n.translate('xpack.lens.indexPattern.ranges.granularityHelpText', {
|
||||
defaultMessage: 'How it works',
|
||||
})}
|
||||
</HelpPopoverButton>
|
||||
}
|
||||
closePopover={() => setIsPopoverOpen(false)}
|
||||
isOpen={isPopoverOpen}
|
||||
title={i18n.translate('xpack.lens.indexPattern.ranges.granularityPopoverTitle', {
|
||||
defaultMessage: 'How granularity interval works',
|
||||
})}
|
||||
>
|
||||
<p>
|
||||
{i18n.translate('xpack.lens.indexPattern.ranges.granularityPopoverBasicExplanation', {
|
||||
defaultMessage:
|
||||
'Interval granularity divides the field into evenly spaced intervals based on the minimum and maximum values for the field.',
|
||||
})}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.indexPattern.ranges.granularityPopoverExplanation"
|
||||
defaultMessage='The size of the interval is chosen to be a "nice" value. It is possible for the chosen
|
||||
interval to stay the same when changing the granularity slider if the "nice" interval is
|
||||
the same. The minimum granularity is 1, and the maximum is
|
||||
{setting}. To change the maximum granularity setting, go to Advanced settings.'
|
||||
values={{
|
||||
setting: <EuiCode>{UI_SETTINGS.HISTOGRAM_MAX_BARS}</EuiCode>,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{i18n.translate('xpack.lens.indexPattern.ranges.granularityPopoverAdvancedExplanation', {
|
||||
defaultMessage:
|
||||
'Intervals are incremented by 10, 5 or 2: for example an interval can be 100 or 0.2 .',
|
||||
})}
|
||||
</p>
|
||||
</HelpPopover>
|
||||
);
|
||||
};
|
||||
|
||||
const BaseRangeEditor = ({
|
||||
maxBars,
|
||||
|
@ -49,12 +101,7 @@ const BaseRangeEditor = ({
|
|||
const granularityLabel = i18n.translate('xpack.lens.indexPattern.ranges.granularity', {
|
||||
defaultMessage: 'Intervals granularity',
|
||||
});
|
||||
const granularityLabelDescription = i18n.translate(
|
||||
'xpack.lens.indexPattern.ranges.granularityDescription',
|
||||
{
|
||||
defaultMessage: 'Divides the field into evenly spaced intervals.',
|
||||
}
|
||||
);
|
||||
|
||||
const decreaseButtonLabel = i18n.translate('xpack.lens.indexPattern.ranges.decreaseButtonLabel', {
|
||||
defaultMessage: 'Decrease granularity',
|
||||
});
|
||||
|
@ -65,21 +112,12 @@ const BaseRangeEditor = ({
|
|||
return (
|
||||
<>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<>
|
||||
{granularityLabel}{' '}
|
||||
<EuiIconTip
|
||||
position="right"
|
||||
content={granularityLabelDescription}
|
||||
type="questionInCircle"
|
||||
color="subdued"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
label={granularityLabel}
|
||||
data-test-subj="indexPattern-ranges-section-label"
|
||||
labelType="legend"
|
||||
fullWidth
|
||||
display="rowCompressed"
|
||||
labelAppend={<GranularityHelpPopover />}
|
||||
>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="xs" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
.lnsXyToolbar__popover {
|
||||
width: 320px;
|
||||
width: 365px;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
EuiColorPickerProps,
|
||||
EuiToolTip,
|
||||
EuiIcon,
|
||||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import { PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import {
|
||||
|
@ -327,9 +328,25 @@ export function XyToolbar(props: VisualizationToolbarProps<State>) {
|
|||
{isFittingEnabled ? (
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
label={i18n.translate('xpack.lens.xyChart.missingValuesLabel', {
|
||||
defaultMessage: 'Missing values',
|
||||
})}
|
||||
label={
|
||||
<>
|
||||
{i18n.translate('xpack.lens.xyChart.missingValuesLabel', {
|
||||
defaultMessage: 'Missing values',
|
||||
})}{' '}
|
||||
<EuiIconTip
|
||||
color="subdued"
|
||||
content={i18n.translate('xpack.lens.xyChart.missingValuesLabelHelpText', {
|
||||
defaultMessage: `Gaps in the data are not shown by default, but can be represented as dotted lines with different modes.`,
|
||||
})}
|
||||
iconProps={{
|
||||
className: 'eui-alignTop',
|
||||
}}
|
||||
position="top"
|
||||
size="s"
|
||||
type="questionInCircle"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<EuiSuperSelect
|
||||
data-test-subj="lnsMissingValuesSelect"
|
||||
|
|
|
@ -11412,7 +11412,6 @@
|
|||
"xpack.lens.indexPattern.ranges.decreaseButtonLabel": "粒度を下げる",
|
||||
"xpack.lens.indexPattern.ranges.deleteRange": "範囲を削除",
|
||||
"xpack.lens.indexPattern.ranges.granularity": "間隔粒度",
|
||||
"xpack.lens.indexPattern.ranges.granularityDescription": "フィールドを均等な間隔に分割します。",
|
||||
"xpack.lens.indexPattern.ranges.increaseButtonLabel": "粒度を上げる",
|
||||
"xpack.lens.indexPattern.ranges.lessThanOrEqualAppend": "≤",
|
||||
"xpack.lens.indexPattern.ranges.lessThanOrEqualTooltip": "以下",
|
||||
|
|
|
@ -11441,7 +11441,6 @@
|
|||
"xpack.lens.indexPattern.ranges.decreaseButtonLabel": "减小粒度",
|
||||
"xpack.lens.indexPattern.ranges.deleteRange": "删除范围",
|
||||
"xpack.lens.indexPattern.ranges.granularity": "时间间隔粒度",
|
||||
"xpack.lens.indexPattern.ranges.granularityDescription": "将字段分成间隔均匀的时间间隔。",
|
||||
"xpack.lens.indexPattern.ranges.increaseButtonLabel": "增加粒度",
|
||||
"xpack.lens.indexPattern.ranges.lessThanOrEqualAppend": "≤",
|
||||
"xpack.lens.indexPattern.ranges.lessThanOrEqualTooltip": "小于或等于",
|
||||
|
|
Loading…
Reference in a new issue