diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md index 22dc92c275670..4b3c915b49c2d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md @@ -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; diff --git a/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_auto_interval.ts b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_auto_interval.ts index 83fd22a618fec..3c1a89015252e 100644 --- a/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_auto_interval.ts +++ b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_auto_interval.ts @@ -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; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 9f0a5b64bde5a..ff3e2ebc89a41 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -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, diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index dd24b1152b22c..e521e468d14a4 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2205,6 +2205,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; @@ -2608,21 +2619,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 diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index 1bdffc90797ac..dc7b291b7120f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -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 (
@@ -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], + }) + } > { id: 'bytes', title: 'Bytes', }), + deserialize: jest.fn().mockReturnValue({ + convert: () => 'formatted', + }), } as unknown) as DataPublicPluginStart['fieldFormats'], } as unknown) as DataPublicPluginStart, core: {} as CoreSetup, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx index d73530ec8a920..1a394584360ad 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx @@ -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} > { + return ( + + + + + {children} + + + ); +}; + +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 ( + + {title && {title}} + + + {children} + + + ); +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx index d9805b337c000..d43dbccd92f83 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx @@ -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: () => , getDisabledStatus(indexPattern, layer) { return checkForDateHistogram( layer, @@ -168,3 +169,79 @@ function MovingAverageParamEditor({ ); } + +const MovingAveragePopup = () => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + return ( + setIsPopoverOpen(!isPopoverOpen)}> + {i18n.translate('xpack.lens.indexPattern.movingAverage.helpText', { + defaultMessage: 'How it works', + })} + + } + closePopover={() => setIsPopoverOpen(false)} + isOpen={isPopoverOpen} + title={i18n.translate('xpack.lens.indexPattern.movingAverage.titleHelp', { + defaultMessage: 'How moving average works', + })} + > +

+ +

+ +

+ +

+ +

+ +

+ +
    +
  • (1 + 2 + 3 + 4 + 5) / 5 = 3
  • +
  • (2 + 3 + 4 + 5 + 6) / 5 = 4
  • +
  • ...
  • +
  • (5 + 6 + 7 + 8 + 9) / 5 = 7
  • +
+ +

+ +

+

+ +

+
    +
  • (1 + 2) / 2 = 1.5
  • +
  • (1 + 2 + 3) / 3 = 2
  • +
  • (1 + 2 + 3 + 4) / 4 = 2.5
  • +
  • (1 + 2 + 3 + 4 + 5) / 5 = 3
  • +
+ +

+ +

+
+ ); +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx index a41cc88c4f292..2e61f4fc3e24d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx @@ -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) => , getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => { if ( type === 'date' && @@ -334,3 +340,77 @@ function restrictedInterval(aggregationRestrictions?: Partial { + 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 ( + setIsPopoverOpen(!isPopoverOpen)}> + {i18n.translate('xpack.lens.indexPattern.dateHistogram.autoHelpText', { + defaultMessage: 'How it works', + })} + + } + closePopover={() => setIsPopoverOpen(false)} + isOpen={isPopoverOpen} + title={i18n.translate('xpack.lens.indexPattern.dateHistogram.titleHelp', { + defaultMessage: 'How auto date histogram works', + })} + > +

+ {i18n.translate('xpack.lens.indexPattern.dateHistogram.autoBasicExplanation', { + defaultMessage: 'The auto date histogram splits a date field into buckets by interval.', + })} +

+ +

+ {UI_SETTINGS.HISTOGRAM_MAX_BARS}, + targetBarSetting: {UI_SETTINGS.HISTOGRAM_BAR_TARGET}, + }} + /> +

+ +

+ {i18n.translate('xpack.lens.indexPattern.dateHistogram.autoAdvancedExplanation', { + defaultMessage: 'The interval follows this logic:', + })} +

+ + ({ + 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', + }), + }, + ]} + /> +
+ ); +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index 36c9cf75d2b6c..7dbc7d3b986a5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -126,6 +126,12 @@ export interface ParamEditorProps { data: DataPublicPluginStart; } +export interface HelpProps { + currentColumn: C; + uiSettings: IUiSettingsClient; + data: DataPublicPluginStart; +} + export type TimeScalingMode = 'disabled' | 'mandatory' | 'optional'; interface BaseOperationDefinitionProps { @@ -201,6 +207,8 @@ interface BaseOperationDefinitionProps { * If set to optional, time scaling won't be enabled by default and can be removed. */ timeScalingMode?: TimeScalingMode; + + getHelpMessage?: (props: HelpProps) => React.ReactNode; } interface BaseBuildColumnArgs { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx index df955be6b490a..ad5c146ff6624 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx @@ -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 ( + setIsPopoverOpen(!isPopoverOpen)}> + {i18n.translate('xpack.lens.indexPattern.ranges.granularityHelpText', { + defaultMessage: 'How it works', + })} + + } + closePopover={() => setIsPopoverOpen(false)} + isOpen={isPopoverOpen} + title={i18n.translate('xpack.lens.indexPattern.ranges.granularityPopoverTitle', { + defaultMessage: 'How granularity interval works', + })} + > +

+ {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.', + })} +

+ +

+ {UI_SETTINGS.HISTOGRAM_MAX_BARS}, + }} + /> +

+ +

+ {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 .', + })} +

+
+ ); +}; 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 ( <> - {granularityLabel}{' '} - - - } + label={granularityLabel} data-test-subj="indexPattern-ranges-section-label" labelType="legend" fullWidth display="rowCompressed" + labelAppend={} > diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss index b9ff6a56d8e35..a2caeb93477fa 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss @@ -1,3 +1,3 @@ .lnsXyToolbar__popover { - width: 320px; + width: 365px; } diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx index 351b1f0d71651..b8bca09bb353c 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx @@ -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) { {isFittingEnabled ? ( + {i18n.translate('xpack.lens.xyChart.missingValuesLabel', { + defaultMessage: 'Missing values', + })}{' '} + + + } >