From 2987165dc966c44b95a80038cc8d305a024dc79d Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Tue, 22 Mar 2022 11:21:15 -0700 Subject: [PATCH 01/29] i18n Absolute/Relative/Now tab labels --- .../date_popover/date_popover_content.tsx | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx index 8fe49449bbb8..01f04dbb9aa0 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx @@ -8,6 +8,7 @@ import React, { FunctionComponent } from 'react'; +import { EuiI18n, useEuiI18n } from '../../../i18n'; import { EuiTabbedContent, EuiTabbedContentProps } from '../../../tabs'; import { EuiText } from '../../../text'; import { EuiButton } from '../../../button'; @@ -55,12 +56,30 @@ export const EuiDatePopoverContent: FunctionComponent ), 'data-test-subj': 'superDatePickerAbsoluteTab', - 'aria-label': `${ariaLabel} Absolute`, + 'aria-label': `${labelPrefix}: ${absoluteLabel}`, }, { id: DATE_MODES.RELATIVE, - name: 'Relative', + name: relativeLabel, content: ( ), 'data-test-subj': 'superDatePickerRelativeTab', - 'aria-label': `${ariaLabel} Relative`, + 'aria-label': `${labelPrefix}: ${relativeLabel}`, }, { id: DATE_MODES.NOW, - name: 'Now', + name: nowLabel, content: ( ), 'data-test-subj': 'superDatePickerNowTab', - 'aria-label': `${ariaLabel} Now`, + 'aria-label': `${labelPrefix}: ${nowLabel}`, }, ]; From 242391a55a3995cbe5d1860be7823fc7914e355b Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Tue, 22 Mar 2022 11:23:00 -0700 Subject: [PATCH 02/29] i18n + DRY start/end date input prepend labels --- .../date_popover/absolute_tab.tsx | 20 +++++++++---------- .../date_popover/date_popover_content.tsx | 2 ++ .../date_popover/relative_tab.tsx | 14 ++----------- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx index 0c37bb5f52c1..80a65ad04c3b 100644 --- a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx @@ -25,12 +25,12 @@ export interface EuiAbsoluteTabProps { onChange: EuiDatePopoverContentProps['onChange']; roundUp: boolean; position: 'start' | 'end'; + labelPrefix: string; utcOffset?: number; } interface EuiAbsoluteTabState { isTextInvalid: boolean; - sentenceCasedPosition: string; textInputValue: string; valueAsMoment: Moment | null; } @@ -44,8 +44,6 @@ export class EuiAbsoluteTab extends Component< constructor(props: EuiAbsoluteTabProps) { super(props); - const sentenceCasedPosition = toSentenceCase(props.position); - const parsedValue = dateMath.parse(props.value, { roundUp: props.roundUp }); const valueAsMoment = parsedValue && parsedValue.isValid() ? parsedValue : moment(); @@ -56,7 +54,6 @@ export class EuiAbsoluteTab extends Component< this.state = { isTextInvalid: false, - sentenceCasedPosition, textInputValue, valueAsMoment, }; @@ -96,13 +93,14 @@ export class EuiAbsoluteTab extends Component< }; render() { - const { dateFormat, timeFormat, locale, utcOffset } = this.props; const { - valueAsMoment, - isTextInvalid, - textInputValue, - sentenceCasedPosition, - } = this.state; + dateFormat, + timeFormat, + locale, + utcOffset, + labelPrefix, + } = this.props; + const { valueAsMoment, isTextInvalid, textInputValue } = this.state; return (
@@ -128,7 +126,7 @@ export class EuiAbsoluteTab extends Component< value={textInputValue} onChange={this.handleTextChange} data-test-subj={'superDatePickerAbsoluteDateInput'} - prepend={{sentenceCasedPosition} date} + prepend={{labelPrefix}} />
diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx index 01f04dbb9aa0..dcf68e38d4e7 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx @@ -89,6 +89,7 @@ export const EuiDatePopoverContent: FunctionComponent ), @@ -106,6 +107,7 @@ export const EuiDatePopoverContent: FunctionComponent ), 'data-test-subj': 'superDatePickerRelativeTab', diff --git a/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx b/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx index d4eb4ff1d33d..ebdf4e665c5b 100644 --- a/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx @@ -8,7 +8,6 @@ import React, { Component, ChangeEventHandler } from 'react'; import dateMath from '@elastic/datemath'; -import { toSentenceCase } from '../../../../services/string/to_case'; import { htmlIdGenerator } from '../../../../services'; import { EuiFlexGroup, EuiFlexItem } from '../../../flex'; import { @@ -44,12 +43,12 @@ export interface EuiRelativeTabProps { onChange: EuiDatePopoverContentProps['onChange']; roundUp?: boolean; position: 'start' | 'end'; + labelPrefix: string; } interface EuiRelativeTabState extends Pick { count: number | undefined; - sentenceCasedPosition: string; } export class EuiRelativeTab extends Component< @@ -58,7 +57,6 @@ export class EuiRelativeTab extends Component< > { state: EuiRelativeTabState = { ...parseRelativeParts(this.props.value), - sentenceCasedPosition: toSentenceCase(this.props.position), }; relativeDateInputNumberDescriptionId = htmlIdGenerator()(); @@ -204,15 +202,7 @@ export class EuiRelativeTab extends Component< compressed value={formattedValue} readOnly - prepend={ - - - - } + prepend={{this.props.labelPrefix}} />

From 53e4e625846cec0d4c6c635ccd6e1023d8ab8930 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Tue, 22 Mar 2022 11:23:15 -0700 Subject: [PATCH 03/29] i18n Absolute date format error --- .../date_popover/absolute_tab.tsx | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx index 80a65ad04c3b..9bb4b1e1b5d0 100644 --- a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx @@ -14,7 +14,7 @@ import dateMath from '@elastic/datemath'; import { EuiDatePicker, EuiDatePickerProps } from '../../date_picker'; import { EuiFormRow, EuiFieldText, EuiFormLabel } from '../../../form'; -import { toSentenceCase } from '../../../../services/string/to_case'; +import { EuiI18n } from '../../../i18n'; import { EuiDatePopoverContentProps } from './date_popover_content'; export interface EuiAbsoluteTabProps { @@ -115,20 +115,28 @@ export class EuiAbsoluteTab extends Component< locale={locale} utcOffset={utcOffset} /> - - {labelPrefix}} - /> - + {(dateFormatError: string) => ( + + {labelPrefix}} + /> + + )} + ); } From 861b5c50475c672ff4b95fe0f3c6ba82dba0af2d Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Tue, 22 Mar 2022 11:23:28 -0700 Subject: [PATCH 04/29] i18n Now tab copy --- .../date_popover/date_popover_content.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx index dcf68e38d4e7..0cd405fafac8 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx @@ -123,8 +123,11 @@ export const EuiDatePopoverContent: FunctionComponent

- Setting the time to "now" means that on every refresh this - time will be set to the time of the refresh. +

- Set {position} date and time to now + {position === 'start' ? ( + + ) : ( + + )}
), From 30614d5e235cc237e395746d95c5fefc711e53fb Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 23 Mar 2022 09:49:34 -0700 Subject: [PATCH 05/29] Convert non-i18n'd time constants to hook with i18n - Example setup of how we'll be handling all other non-i18n'd constants used throughout datepicker --- .../quick_select_popover/quick_select.tsx | 11 ---- .../super_date_picker/time_options.tsx | 66 +++++++++++++++++++ .../super_date_picker/time_units.ts | 29 -------- 3 files changed, 66 insertions(+), 40 deletions(-) create mode 100644 src/components/date_picker/super_date_picker/time_options.tsx delete mode 100644 src/components/date_picker/super_date_picker/time_units.ts diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx index f4eeabe65a83..51f512bffe0a 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx @@ -26,17 +26,6 @@ import { ApplyTime, QuickSelect, TimeUnitId } from '../../types'; import { keysOf } from '../../../common'; import { parseTimeParts } from './quick_select_utils'; -const LAST = 'last'; -const NEXT = 'next'; - -const timeTenseOptions = [ - { value: LAST, text: 'Last' }, - { value: NEXT, text: 'Next' }, -]; -const timeUnitsOptions = keysOf(timeUnits).map((key) => { - return { value: key, text: `${timeUnits[key]}s` }; -}); - type EuiQuickSelectState = QuickSelect; export interface EuiQuickSelectProps { diff --git a/src/components/date_picker/super_date_picker/time_options.tsx b/src/components/date_picker/super_date_picker/time_options.tsx new file mode 100644 index 000000000000..e22a96c5d78c --- /dev/null +++ b/src/components/date_picker/super_date_picker/time_options.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { keysOf } from '../../common'; +import { useEuiI18n } from '../../i18n'; +import { EuiSelectOption } from '../../form'; + +import { TimeUnitId, TimeUnitLabel, TimeUnitLabelPlural } from '../types'; + +export const LAST = 'last'; +export const NEXT = 'next'; + +export type TimeOptions = { + timeTenseOptions: EuiSelectOption[]; + timeUnits: { [id in TimeUnitId]: TimeUnitLabel }; + timeUnitsOptions: EuiSelectOption[]; + timeUnitsPlural: { [id in TimeUnitId]: TimeUnitLabelPlural }; +}; + +export const useI18nTimeOptions = () => { + const timeTenseOptions = [ + { + value: LAST, + text: useEuiI18n('euiTimeOptions.last', 'Last'), + }, + { + value: NEXT, + text: useEuiI18n('euiTimeOptions.next', 'Next'), + }, + ]; + + const timeUnits = { + s: useEuiI18n('euiTimeOptions.second', 'second'), + m: useEuiI18n('euiTimeOptions.minute', 'minute'), + h: useEuiI18n('euiTimeOptions.hour', 'hour'), + d: useEuiI18n('euiTimeOptions.day', 'day'), + w: useEuiI18n('euiTimeOptions.week', 'week'), + M: useEuiI18n('euiTimeOptions.month', 'month'), + y: useEuiI18n('euiTimeOptions.year', 'year'), + }; + const timeUnitsOptions = keysOf(timeUnits).map((key) => { + return { value: key, text: `${timeUnits[key]}s` }; + }); + + const timeUnitsPlural = { + s: useEuiI18n('euiTimeOptions.seconds', 'seconds'), + m: useEuiI18n('euiTimeOptions.minutes', 'minutes'), + h: useEuiI18n('euiTimeOptions.hours', 'hours'), + d: useEuiI18n('euiTimeOptions.days', 'days'), + w: useEuiI18n('euiTimeOptions.weeks', 'weeks'), + M: useEuiI18n('euiTimeOptions.months', 'months'), + y: useEuiI18n('euiTimeOptions.years', 'years'), + }; + + return { + timeTenseOptions, + timeUnits, + timeUnitsOptions, + timeUnitsPlural, + }; +}; diff --git a/src/components/date_picker/super_date_picker/time_units.ts b/src/components/date_picker/super_date_picker/time_units.ts deleted file mode 100644 index c6418cff96d8..000000000000 --- a/src/components/date_picker/super_date_picker/time_units.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { TimeUnitId, TimeUnitLabel, TimeUnitLabelPlural } from '../types'; - -export const timeUnits: { [id in TimeUnitId]: TimeUnitLabel } = { - s: 'second', - m: 'minute', - h: 'hour', - d: 'day', - w: 'week', - M: 'month', - y: 'year', -}; - -export const timeUnitsPlural: { [id in TimeUnitId]: TimeUnitLabelPlural } = { - s: 'seconds', - m: 'minutes', - h: 'hours', - d: 'days', - w: 'weeks', - M: 'months', - y: 'years', -}; From 5def024a2aeaf304caf2199854c5a346aefe5090 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 23 Mar 2022 10:58:11 -0700 Subject: [PATCH 06/29] Add render function that passes i18n strings/constants to EuiSuperDatePicker + update external/internal required props accordingly - there were a lot of props marked required that weren't actually required because they had defaults --- .../super_date_picker_custom_quick_select.tsx | 2 +- .../super_date_picker_pattern.tsx | 2 +- .../super_date_picker.test.tsx | 77 ++++++++++++------- .../super_date_picker/super_date_picker.tsx | 55 +++++++++---- .../super_date_picker/time_options.tsx | 7 ++ 5 files changed, 98 insertions(+), 45 deletions(-) diff --git a/src-docs/src/views/super_date_picker/super_date_picker_custom_quick_select.tsx b/src-docs/src/views/super_date_picker/super_date_picker_custom_quick_select.tsx index 7310922282ea..39569ff92543 100644 --- a/src-docs/src/views/super_date_picker/super_date_picker_custom_quick_select.tsx +++ b/src-docs/src/views/super_date_picker/super_date_picker_custom_quick_select.tsx @@ -43,7 +43,7 @@ export default () => { }; const [recentlyUsedRanges, setRecentlyUsedRanges] = useState< - EuiSuperDatePickerProps['recentlyUsedRanges'] + NonNullable >([]); const [isLoading, setIsLoading] = useState(false); const [start, setStart] = useState('now-30m'); diff --git a/src-docs/src/views/super_date_picker/super_date_picker_pattern.tsx b/src-docs/src/views/super_date_picker/super_date_picker_pattern.tsx index d4c7dfbdc979..c9eb92002595 100644 --- a/src-docs/src/views/super_date_picker/super_date_picker_pattern.tsx +++ b/src-docs/src/views/super_date_picker/super_date_picker_pattern.tsx @@ -23,7 +23,7 @@ const sampleItems = [ export default () => { const [recentlyUsedRanges, setRecentlyUsedRanges] = useState< - EuiSuperDatePickerProps['recentlyUsedRanges'] + NonNullable >([]); const [isLoading, setIsLoading] = useState(false); const [refreshInterval, setRefreshInterval] = useState(1000); diff --git a/src/components/date_picker/super_date_picker/super_date_picker.test.tsx b/src/components/date_picker/super_date_picker/super_date_picker.test.tsx index 0c6fa0463a8a..a81705a63f80 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.test.tsx +++ b/src/components/date_picker/super_date_picker/super_date_picker.test.tsx @@ -7,29 +7,42 @@ */ import React from 'react'; -import { mount, shallow } from 'enzyme'; +import { mount, shallow, ReactWrapper } from 'enzyme'; import { EuiSuperDatePicker, EuiSuperDatePickerProps, + EuiSuperDatePickerInternal, } from './super_date_picker'; import { EuiButton } from '../../button'; const noop = () => {}; +// Test utils to handle diving into EuiSuperDatePickerInternal +const findInternalInstance = ( + wrapper: ReactWrapper +): [EuiSuperDatePickerInternal, ReactWrapper] => { + const component = wrapper.find('EuiSuperDatePickerInternal'); + const instance = component.instance() as EuiSuperDatePickerInternal; + return [instance, component]; +}; +const shallowAndDive = (component: React.ReactElement) => + shallow(component).dive().dive(); + describe('EuiSuperDatePicker', () => { test('is rendered', () => { - const component = shallow(); + const component = shallowAndDive( + + ); expect(component).toMatchSnapshot(); }); test('refresh is disabled by default', () => { // By default we expect `asyncInterval` to be not set. - const componentPaused = mount( - - ); - const instancePaused = componentPaused.instance(); + const component = mount(); + const [instancePaused, componentPaused] = findInternalInstance(component); + expect(instancePaused.asyncInterval).toBe(undefined); expect(componentPaused.prop('isPaused')).toBe(true); }); @@ -38,31 +51,38 @@ describe('EuiSuperDatePicker', () => { // If refresh is enabled via `isPaused/onRefresh` we expect // `asyncInterval` to be present and `asyncInterval.isStopped` to be `false`. const onRefresh = jest.fn(); - const componentRefresh = mount( + const component = mount( ); - const instanceRefresh = componentRefresh.instance(); + const [instanceRefresh, componentRefresh] = findInternalInstance(component); + expect(typeof instanceRefresh.asyncInterval).toBe('object'); expect(instanceRefresh.asyncInterval!.isStopped).toBe(false); expect(componentRefresh.prop('isPaused')).toBe(false); // If we update the prop `isPaused` we expect the interval to be stopped too. - componentRefresh.setProps({ isPaused: true }); - const instanceUpdatedPaused = componentRefresh.instance(); + component.setProps({ isPaused: true }); + const [ + instanceUpdatedPaused, + componentUpdatedPaused, + ] = findInternalInstance(component); expect(typeof instanceUpdatedPaused.asyncInterval).toBe('object'); expect(instanceUpdatedPaused.asyncInterval!.isStopped).toBe(true); - expect(componentRefresh.prop('isPaused')).toBe(true); + expect(componentUpdatedPaused.prop('isPaused')).toBe(true); // Let's start refresh again for a final sanity check. - componentRefresh.setProps({ isPaused: false }); - const instanceUpdatedRefresh = componentRefresh.instance(); + component.setProps({ isPaused: false }); + const [ + instanceUpdatedRefresh, + componentUpdatedRefresh, + ] = findInternalInstance(component); expect(typeof instanceUpdatedRefresh.asyncInterval).toBe('object'); expect(instanceUpdatedRefresh.asyncInterval!.isStopped).toBe(false); - expect(componentRefresh.prop('isPaused')).toBe(false); + expect(componentUpdatedRefresh.prop('isPaused')).toBe(false); }); test('Listen for consecutive super date picker refreshes', async () => { @@ -70,7 +90,7 @@ describe('EuiSuperDatePicker', () => { const onRefresh = jest.fn(); - const componentRefresh = mount( + const component = mount( { refreshInterval={10} /> ); + const [instanceRefresh] = findInternalInstance(component); - const instanceRefresh = componentRefresh.instance(); expect(typeof instanceRefresh.asyncInterval).toBe('object'); jest.advanceTimersByTime(10); @@ -97,7 +117,7 @@ describe('EuiSuperDatePicker', () => { const onRefresh = jest.fn(); - const componentRefresh = mount( + const component = mount( { refreshInterval={10} /> ); - - const instanceRefresh = componentRefresh.instance(); + const [instanceRefresh] = findInternalInstance(component); jest.advanceTimersByTime(10); expect(typeof instanceRefresh.asyncInterval).toBe('object'); await instanceRefresh.asyncInterval!.__pendingFn; - componentRefresh.setProps({ isPaused: true, refreshInterval: 0 }); + component.setProps({ isPaused: true, refreshInterval: 0 }); jest.advanceTimersByTime(10); await instanceRefresh.asyncInterval!.__pendingFn; @@ -140,14 +159,14 @@ describe('EuiSuperDatePicker', () => { describe('showUpdateButton', () => { test('can be false', () => { - const component = shallow( + const component = shallowAndDive( ); expect(component).toMatchSnapshot(); }); test('can be iconOnly', () => { - const component = shallow( + const component = shallowAndDive( ); expect(component).toMatchSnapshot(); @@ -155,7 +174,7 @@ describe('EuiSuperDatePicker', () => { }); test('accepts data-test-subj and passes to EuiFormControlLayout', () => { - const component = shallow( + const component = shallowAndDive( { describe('width', () => { test('can be full', () => { - const component = shallow( + const component = shallowAndDive( ); expect(component).toMatchSnapshot(); }); test('can be auto', () => { - const component = shallow( + const component = shallowAndDive( ); expect(component).toMatchSnapshot(); @@ -181,7 +200,7 @@ describe('EuiSuperDatePicker', () => { describe('compressed', () => { test('is rendered', () => { - const component = shallow( + const component = shallowAndDive( ); expect(component).toMatchSnapshot(); @@ -190,7 +209,7 @@ describe('EuiSuperDatePicker', () => { describe('isQuickSelectOnly', () => { test('is rendered', () => { - const component = shallow( + const component = shallowAndDive( ); expect(component).toMatchSnapshot(); @@ -199,7 +218,7 @@ describe('EuiSuperDatePicker', () => { describe('isAutoRefreshOnly', () => { it('is rendered', () => { - const component = shallow( + const component = shallowAndDive( { }); it('passes required props', () => { - const component = shallow( + const component = shallowAndDive( void; - recentlyUsedRanges: DurationRange[]; + recentlyUsedRanges?: DurationRange[]; /** * Refresh interval in milliseconds */ - refreshInterval: Milliseconds; + refreshInterval?: Milliseconds; - start: ShortDate; - end: ShortDate; + start?: ShortDate; + end?: ShortDate; /** * Specifies the formatted used when displaying times */ - timeFormat: string; + timeFormat?: string; utcOffset?: number; /** * Set showUpdateButton to false to immediately invoke onTimeChange for all start and end changes. */ - showUpdateButton: boolean | 'iconOnly'; + showUpdateButton?: boolean | 'iconOnly'; /** * Hides the actual input reducing to just the quick select button. @@ -142,6 +143,20 @@ export type EuiSuperDatePickerProps = CommonProps & { updateButtonProps?: EuiSuperUpdateButtonProps; }; +type EuiSuperDatePickerInternalProps = EuiSuperDatePickerProps & { + timeOptions: TimeOptions; + // The below options are marked as required because they have default fallbacks + commonlyUsedRanges: DurationRange[]; + recentlyUsedRanges: DurationRange[]; + start: ShortDate; + end: ShortDate; + refreshInterval: Milliseconds; + dateFormat: string; + timeFormat: string; + isPaused: boolean; + isDisabled: boolean; +}; + interface EuiSuperDatePickerState { end: ShortDate; hasChanged: boolean; @@ -176,8 +191,8 @@ function isRangeInvalid(start: ShortDate, end: ShortDate) { return isInvalid; } -export class EuiSuperDatePicker extends Component< - EuiSuperDatePickerProps, +export class EuiSuperDatePickerInternal extends Component< + EuiSuperDatePickerInternalProps, EuiSuperDatePickerState > { static defaultProps = { @@ -216,7 +231,7 @@ export class EuiSuperDatePicker extends Component< }; static getDerivedStateFromProps( - nextProps: EuiSuperDatePickerProps, + nextProps: EuiSuperDatePickerInternalProps, prevState: EuiSuperDatePickerState ) { if ( @@ -599,3 +614,15 @@ export class EuiSuperDatePicker extends Component< ); } } + +// Because EuiSuperDatePicker is a class component and not a functional component, +// we have to use a render prop here in order for us to pass i18n'd strings/objects/etc +// to all underlying usages of our timeOptions constants. If someday we convert +// EuiSuperDatePicker to an FC, we can likely get rid of this wrapper. +export const EuiSuperDatePicker = (props: EuiSuperDatePickerProps) => ( + + {(timeOptions) => ( + + )} + +); diff --git a/src/components/date_picker/super_date_picker/time_options.tsx b/src/components/date_picker/super_date_picker/time_options.tsx index e22a96c5d78c..8757fabe0cfa 100644 --- a/src/components/date_picker/super_date_picker/time_options.tsx +++ b/src/components/date_picker/super_date_picker/time_options.tsx @@ -64,3 +64,10 @@ export const useI18nTimeOptions = () => { timeUnitsPlural, }; }; + +export const RenderI18nTimeOptions = (props: { + children: (args: TimeOptions) => any; +}) => { + const timeOptions = useI18nTimeOptions(); + return props.children(timeOptions); +}; From 94984f6f23a03c88def91132bd4742bf8f152dfc Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 23 Mar 2022 11:07:16 -0700 Subject: [PATCH 07/29] Waterfall time options constants as props to child components that need it --- .../super_date_picker/date_popover/date_popover_button.tsx | 2 ++ .../super_date_picker/date_popover/date_popover_content.tsx | 4 ++++ .../super_date_picker/date_popover/relative_tab.tsx | 4 +++- .../super_date_picker/quick_select_popover/quick_select.tsx | 6 ++++-- .../quick_select_popover/quick_select_popover.tsx | 4 ++++ .../date_picker/super_date_picker/super_date_picker.tsx | 5 +++++ 6 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx index dec4d90f9997..b987cc29664b 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx @@ -17,6 +17,7 @@ import { LocaleSpecifier } from 'moment'; // eslint-disable-line import/named import { useEuiI18n } from '../../../i18n'; import { EuiPopover, EuiPopoverProps } from '../../../popover'; +import { TimeOptions } from '../time_options'; import { formatTimeString } from '../pretty_duration'; import { EuiDatePopoverContent, @@ -130,6 +131,7 @@ export const EuiDatePopoverButton: FunctionComponent locale={locale} position={position} utcOffset={utcOffset} + timeOptions={timeOptions} /> ); diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx index 0cd405fafac8..422b4d06b49c 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx @@ -16,6 +16,7 @@ import { EuiButton } from '../../../button'; import { EuiAbsoluteTab } from './absolute_tab'; import { EuiRelativeTab } from './relative_tab'; +import { TimeOptions } from '../time_options'; import { getDateMode, DATE_MODES, @@ -33,6 +34,7 @@ export interface EuiDatePopoverContentProps { locale?: LocaleSpecifier; position: 'start' | 'end'; utcOffset?: number; + timeOptions: TimeOptions; } export const EuiDatePopoverContent: FunctionComponent = ({ @@ -44,6 +46,7 @@ export const EuiDatePopoverContent: FunctionComponent { const onTabClick: EuiTabbedContentProps['onTabClick'] = (selectedTab) => { switch (selectedTab.id) { @@ -108,6 +111,7 @@ export const EuiDatePopoverContent: FunctionComponent ), 'data-test-subj': 'superDatePickerRelativeTab', diff --git a/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx b/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx index ebdf4e665c5b..a79664d33067 100644 --- a/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx @@ -22,7 +22,7 @@ import { } from '../../../form'; import { EuiSpacer } from '../../../spacer'; -import { timeUnits } from '../time_units'; +import { TimeOptions } from '../time_options'; import { relativeOptions } from '../relative_options'; import { parseRelativeParts, @@ -44,6 +44,7 @@ export interface EuiRelativeTabProps { roundUp?: boolean; position: 'start' | 'end'; labelPrefix: string; + timeOptions: TimeOptions; } interface EuiRelativeTabState @@ -105,6 +106,7 @@ export class EuiRelativeTab extends Component< }; render() { + const { timeUnits } = this.props.timeOptions; const { count, unit } = this.state; const invalidDate = this.props.value === INVALID_DATE; const invalidValue = count === undefined || count < 0; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx index 51f512bffe0a..c3711c88b7f7 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx @@ -20,10 +20,9 @@ import { EuiSpacer } from '../../../spacer'; import { EuiSelect, EuiFieldNumber } from '../../../form'; import { EuiToolTip } from '../../../tool_tip'; import { EuiI18n } from '../../../i18n'; -import { timeUnits } from '../time_units'; import { EuiScreenReaderOnly } from '../../../accessibility'; import { ApplyTime, QuickSelect, TimeUnitId } from '../../types'; -import { keysOf } from '../../../common'; +import { TimeOptions, NEXT } from '../time_options'; import { parseTimeParts } from './quick_select_utils'; type EuiQuickSelectState = QuickSelect; @@ -33,6 +32,7 @@ export interface EuiQuickSelectProps { start: string; end: string; prevQuickSelect?: EuiQuickSelectState; + timeOptions: TimeOptions; } export class EuiQuickSelect extends Component< @@ -150,6 +150,8 @@ export class EuiQuickSelect extends Component< render() { const { timeTense, timeValue, timeUnits } = this.state; + const { timeTenseOptions, timeUnitsOptions } = this.props.timeOptions; + const matchedTimeUnit = timeUnitsOptions.find( ({ value }) => value === timeUnits ); diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx index c581211d7c81..f333e9f8396b 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx @@ -20,6 +20,7 @@ import { EuiQuickSelect } from './quick_select'; import { EuiCommonlyUsedTimeRanges } from './commonly_used_time_ranges'; import { EuiRecentlyUsed } from './recently_used'; import { EuiRefreshInterval } from '../../auto_refresh/refresh_interval'; +import { TimeOptions } from '../time_options'; import { DurationRange, ApplyRefreshInterval, @@ -40,6 +41,7 @@ export interface EuiQuickSelectPopoverProps { recentlyUsedRanges: DurationRange[]; refreshInterval: number; start: string; + timeOptions: TimeOptions; } interface EuiQuickSelectPopoverState { @@ -90,6 +92,7 @@ export class EuiQuickSelectPopover extends Component< end, recentlyUsedRanges, start, + timeOptions, } = this.props; const { prevQuickSelect } = this.state; @@ -100,6 +103,7 @@ export class EuiQuickSelectPopover extends Component< start={start} end={end} prevQuickSelect={prevQuickSelect} + timeOptions={timeOptions} /> {commonlyUsedRanges.length > 0 && } } endDateControl={ @@ -467,6 +469,7 @@ export class EuiSuperDatePickerInternal extends Component< isOpen={this.state.isEndDatePopoverOpen} onPopoverToggle={this.onEndDatePopoverToggle} onPopoverClose={this.onEndDatePopoverClose} + timeOptions={timeOptions} /> } /> @@ -518,6 +521,7 @@ export class EuiSuperDatePickerInternal extends Component< render() { const { commonlyUsedRanges, + timeOptions, customQuickSelectPanels, dateFormat, end, @@ -564,6 +568,7 @@ export class EuiSuperDatePickerInternal extends Component< recentlyUsedRanges={recentlyUsedRanges} refreshInterval={refreshInterval} start={start} + timeOptions={timeOptions} /> ); From d876e4df46caaaa684f98fdc3e33f89e0c278ceb Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 23 Mar 2022 11:17:26 -0700 Subject: [PATCH 08/29] i18n relative tab dropdown options --- .../date_popover/relative_tab.tsx | 3 +- .../super_date_picker/relative_options.ts | 18 ----- .../super_date_picker/time_options.tsx | 68 ++++++++++++++++++- 3 files changed, 68 insertions(+), 21 deletions(-) diff --git a/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx b/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx index a79664d33067..a8ca449b63c5 100644 --- a/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx @@ -23,7 +23,6 @@ import { import { EuiSpacer } from '../../../spacer'; import { TimeOptions } from '../time_options'; -import { relativeOptions } from '../relative_options'; import { parseRelativeParts, toRelativeStringFromParts, @@ -106,7 +105,7 @@ export class EuiRelativeTab extends Component< }; render() { - const { timeUnits } = this.props.timeOptions; + const { timeUnits, relativeOptions } = this.props.timeOptions; const { count, unit } = this.state; const invalidDate = this.props.value === INVALID_DATE; const invalidValue = count === undefined || count < 0; diff --git a/src/components/date_picker/super_date_picker/relative_options.ts b/src/components/date_picker/super_date_picker/relative_options.ts index 43eea68886ce..8190ab83d940 100644 --- a/src/components/date_picker/super_date_picker/relative_options.ts +++ b/src/components/date_picker/super_date_picker/relative_options.ts @@ -8,24 +8,6 @@ import { RelativeOption, TimeUnitId } from '../types'; -export const relativeOptions: RelativeOption[] = [ - { text: 'Seconds ago', value: 's' }, - { text: 'Minutes ago', value: 'm' }, - { text: 'Hours ago', value: 'h' }, - { text: 'Days ago', value: 'd' }, - { text: 'Weeks ago', value: 'w' }, - { text: 'Months ago', value: 'M' }, - { text: 'Years ago', value: 'y' }, - - { text: 'Seconds from now', value: 's+' }, - { text: 'Minutes from now', value: 'm+' }, - { text: 'Hours from now', value: 'h+' }, - { text: 'Days from now', value: 'd+' }, - { text: 'Weeks from now', value: 'w+' }, - { text: 'Months from now', value: 'M+' }, - { text: 'Years from now', value: 'y+' }, -]; - const timeUnitIds = relativeOptions .map(({ value }) => value) .filter((value) => !value.includes('+')) as TimeUnitId[]; diff --git a/src/components/date_picker/super_date_picker/time_options.tsx b/src/components/date_picker/super_date_picker/time_options.tsx index 8757fabe0cfa..08d8287966bf 100644 --- a/src/components/date_picker/super_date_picker/time_options.tsx +++ b/src/components/date_picker/super_date_picker/time_options.tsx @@ -10,7 +10,12 @@ import { keysOf } from '../../common'; import { useEuiI18n } from '../../i18n'; import { EuiSelectOption } from '../../form'; -import { TimeUnitId, TimeUnitLabel, TimeUnitLabelPlural } from '../types'; +import { + TimeUnitId, + TimeUnitLabel, + TimeUnitLabelPlural, + RelativeOption, +} from '../types'; export const LAST = 'last'; export const NEXT = 'next'; @@ -20,6 +25,7 @@ export type TimeOptions = { timeUnits: { [id in TimeUnitId]: TimeUnitLabel }; timeUnitsOptions: EuiSelectOption[]; timeUnitsPlural: { [id in TimeUnitId]: TimeUnitLabelPlural }; + relativeOptions: RelativeOption[]; }; export const useI18nTimeOptions = () => { @@ -57,11 +63,71 @@ export const useI18nTimeOptions = () => { y: useEuiI18n('euiTimeOptions.years', 'years'), }; + const relativeOptions: RelativeOption[] = [ + { + text: useEuiI18n('euiTimeOptions.secondsAgo', 'Seconds ago'), + value: 's', + }, + { + text: useEuiI18n('euiTimeOptions.minutesAgo', 'Minutes ago'), + value: 'm', + }, + { + text: useEuiI18n('euiTimeOptions.hoursAgo', 'Hours ago'), + value: 'h', + }, + { + text: useEuiI18n('euiTimeOptions.daysAgo', 'Days ago'), + value: 'd', + }, + { + text: useEuiI18n('euiTimeOptions.weeksAgo', 'Weeks ago'), + value: 'w', + }, + { + text: useEuiI18n('euiTimeOptions.monthsAgo', 'Months ago'), + value: 'M', + }, + { + text: useEuiI18n('euiTimeOptions.yearsAgo', 'Years ago'), + value: 'y', + }, + { + text: useEuiI18n('euiTimeOptions.secondsFromNow', 'Seconds from now'), + value: 's+', + }, + { + text: useEuiI18n('euiTimeOptions.minutesFromNow', 'Minutes from now'), + value: 'm+', + }, + { + text: useEuiI18n('euiTimeOptions.hoursFromNow', 'Hours from now'), + value: 'h+', + }, + { + text: useEuiI18n('euiTimeOptions.daysFromNow', 'Days from now'), + value: 'd+', + }, + { + text: useEuiI18n('euiTimeOptions.weeksFromNow', 'Weeks from now'), + value: 'w+', + }, + { + text: useEuiI18n('euiTimeOptions.monthsFromNow', 'Months from now'), + value: 'M+', + }, + { + text: useEuiI18n('euiTimeOptions.yearsFromNow', 'Years from now'), + value: 'y+', + }, + ]; + return { timeTenseOptions, timeUnits, timeUnitsOptions, timeUnitsPlural, + relativeOptions, }; }; From fd4050c46009b8116701a86ab2d88d69e6b7d8d8 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 23 Mar 2022 11:19:22 -0700 Subject: [PATCH 09/29] DRY out `relativeUnitsFromLargestToSmallest` - it has no i18n strings and can be a constant --- .../quick_select_utils.ts | 6 ++--- .../relative_options.test.ts | 26 ------------------- .../super_date_picker/relative_options.ts | 15 ----------- .../super_date_picker/relative_utils.ts | 11 +++++++- 4 files changed, 12 insertions(+), 46 deletions(-) delete mode 100644 src/components/date_picker/super_date_picker/relative_options.test.ts delete mode 100644 src/components/date_picker/super_date_picker/relative_options.ts diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_utils.ts b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_utils.ts index da6e65e9c385..aad4840df94b 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_utils.ts +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_utils.ts @@ -9,12 +9,10 @@ import moment from 'moment'; import dateMath from '@elastic/datemath'; import { isString } from '../../../../services/predicate'; -import { relativeUnitsFromLargestToSmallest } from '../relative_options'; import { DATE_MODES } from '../date_modes'; import { QuickSelect, TimeUnitId } from '../../types'; - -const LAST = 'last'; -const NEXT = 'next'; +import { LAST, NEXT } from '../time_options'; +import { relativeUnitsFromLargestToSmallest } from '../relative_utils'; const isNow = (value: string) => value === DATE_MODES.NOW; diff --git a/src/components/date_picker/super_date_picker/relative_options.test.ts b/src/components/date_picker/super_date_picker/relative_options.test.ts deleted file mode 100644 index 3af0f629e840..000000000000 --- a/src/components/date_picker/super_date_picker/relative_options.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { relativeUnitsFromLargestToSmallest } from './relative_options'; - -describe('relativeUnitsFromLargestToSmallest', () => { - test('relativeUnitsFromLargestToSmallest length', () => { - expect(relativeUnitsFromLargestToSmallest.length).toBe(7); - }); - test('relativeUnitsFromLargestToSmallest order', () => { - expect(relativeUnitsFromLargestToSmallest).toEqual([ - 'y', - 'M', - 'w', - 'd', - 'h', - 'm', - 's', - ]); - }); -}); diff --git a/src/components/date_picker/super_date_picker/relative_options.ts b/src/components/date_picker/super_date_picker/relative_options.ts deleted file mode 100644 index 8190ab83d940..000000000000 --- a/src/components/date_picker/super_date_picker/relative_options.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { RelativeOption, TimeUnitId } from '../types'; - -const timeUnitIds = relativeOptions - .map(({ value }) => value) - .filter((value) => !value.includes('+')) as TimeUnitId[]; - -export const relativeUnitsFromLargestToSmallest = timeUnitIds.reverse(); diff --git a/src/components/date_picker/super_date_picker/relative_utils.ts b/src/components/date_picker/super_date_picker/relative_utils.ts index 2ff8bd33d7d0..9ad0b211f6c6 100644 --- a/src/components/date_picker/super_date_picker/relative_utils.ts +++ b/src/components/date_picker/super_date_picker/relative_utils.ts @@ -11,11 +11,20 @@ import moment from 'moment'; import { get } from '../../../services/objects'; import { isString } from '../../../services/predicate'; -import { relativeUnitsFromLargestToSmallest } from './relative_options'; import { TimeUnitId, RelativeParts } from '../types'; const ROUND_DELIMETER = '/'; +export const relativeUnitsFromLargestToSmallest: TimeUnitId[] = [ + 'y', + 'M', + 'w', + 'd', + 'h', + 'm', + 's', +]; + export function parseRelativeParts(value: string): RelativeParts { const matches = isString(value) && From c18988b16c2297c0f389f191cd6bd087ea183b6d Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 23 Mar 2022 11:25:01 -0700 Subject: [PATCH 10/29] i18n refresh dropdown options --- .../auto_refresh/refresh_interval.tsx | 148 +++++++++--------- .../super_date_picker/time_options.tsx | 8 + 2 files changed, 85 insertions(+), 71 deletions(-) diff --git a/src/components/date_picker/auto_refresh/refresh_interval.tsx b/src/components/date_picker/auto_refresh/refresh_interval.tsx index d5f614e0b3ee..9330fb1d7e19 100644 --- a/src/components/date_picker/auto_refresh/refresh_interval.tsx +++ b/src/components/date_picker/auto_refresh/refresh_interval.tsx @@ -12,24 +12,15 @@ import React, { KeyboardEventHandler, } from 'react'; import { EuiI18n } from '../../i18n'; -import { keysOf } from '../../common'; import { EuiFlexGroup, EuiFlexItem } from '../../flex'; import { EuiSelect, EuiFieldNumber, EuiFormLabel, EuiSwitch } from '../../form'; import { htmlIdGenerator } from '../../../services'; import { EuiScreenReaderOnly } from '../../accessibility'; import { - Milliseconds, - TimeUnitId, - RelativeOption, - ApplyRefreshInterval, -} from '../types'; -import { timeUnits, timeUnitsPlural } from '../super_date_picker/time_units'; - -const refreshUnitsOptions: RelativeOption[] = keysOf(timeUnits) - .filter( - (timeUnit) => timeUnit === 'h' || timeUnit === 'm' || timeUnit === 's' - ) - .map((timeUnit) => ({ value: timeUnit, text: timeUnitsPlural[timeUnit] })); + RenderI18nTimeOptions, + TimeOptions, +} from '../super_date_picker/time_options'; +import { Milliseconds, TimeUnitId, ApplyRefreshInterval } from '../types'; const MILLISECONDS_IN_SECOND = 1000; const MILLISECONDS_IN_MINUTE = MILLISECONDS_IN_SECOND * 60; @@ -174,7 +165,9 @@ export class EuiRefreshInterval extends Component< }); }; - render() { + renderScreenReaderText = ( + refreshUnitsOptions: TimeOptions['refreshUnitsOptions'] + ) => { const { isPaused } = this.props; const { value, units } = this.state; @@ -202,63 +195,76 @@ export class EuiRefreshInterval extends Component< ); return ( -
- - - - - - } - /> - - - - - - - - - -

{fullDescription}

-
-
+ +

{fullDescription}

+
+ ); + }; + + render() { + const { isPaused } = this.props; + const { value, units } = this.state; + + return ( + + {({ refreshUnitsOptions }) => ( +
+ + + + + + } + /> + + + + + + + + + {this.renderScreenReaderText(refreshUnitsOptions)} +
+ )} +
); } } diff --git a/src/components/date_picker/super_date_picker/time_options.tsx b/src/components/date_picker/super_date_picker/time_options.tsx index 08d8287966bf..f467f5f428f6 100644 --- a/src/components/date_picker/super_date_picker/time_options.tsx +++ b/src/components/date_picker/super_date_picker/time_options.tsx @@ -26,6 +26,7 @@ export type TimeOptions = { timeUnitsOptions: EuiSelectOption[]; timeUnitsPlural: { [id in TimeUnitId]: TimeUnitLabelPlural }; relativeOptions: RelativeOption[]; + refreshUnitsOptions: RelativeOption[]; }; export const useI18nTimeOptions = () => { @@ -122,12 +123,19 @@ export const useI18nTimeOptions = () => { }, ]; + const refreshUnitsOptions = keysOf(timeUnits) + .filter( + (timeUnit) => timeUnit === 'h' || timeUnit === 'm' || timeUnit === 's' + ) + .map((timeUnit) => ({ value: timeUnit, text: timeUnitsPlural[timeUnit] })); + return { timeTenseOptions, timeUnits, timeUnitsOptions, timeUnitsPlural, relativeOptions, + refreshUnitsOptions, }; }; From 2709cc05aadd21a71c14c16ebeea46b00e3abbc6 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 23 Mar 2022 11:35:36 -0700 Subject: [PATCH 11/29] i18n refresh shorthand units / convert prettyInterval to hook + refactor prettyInterval structure with `useI18nUnits` helper - because hooks can't be called conditionally, I had to rewrite a good amount to try and keep the code readable --- .../date_picker/auto_refresh/auto_refresh.tsx | 8 +- .../super_date_picker/pretty_interval.test.ts | 91 +++++++++++++------ .../super_date_picker/pretty_interval.ts | 72 ++++++++++----- .../super_date_picker/time_options.tsx | 9 ++ 4 files changed, 128 insertions(+), 52 deletions(-) diff --git a/src/components/date_picker/auto_refresh/auto_refresh.tsx b/src/components/date_picker/auto_refresh/auto_refresh.tsx index 7602ddb988eb..c9963f4cf135 100644 --- a/src/components/date_picker/auto_refresh/auto_refresh.tsx +++ b/src/components/date_picker/auto_refresh/auto_refresh.tsx @@ -16,7 +16,7 @@ import { import { EuiInputPopover, EuiPopover } from '../../popover'; import { useEuiI18n } from '../../i18n'; -import { prettyInterval } from '../super_date_picker/pretty_interval'; +import { usePrettyInterval } from '../super_date_picker/pretty_interval'; import { EuiRefreshInterval, EuiRefreshIntervalProps, @@ -75,7 +75,7 @@ export const EuiAutoRefresh: FunctionComponent = ({ } readOnly={readOnly} disabled={isDisabled} - value={prettyInterval(Boolean(isPaused), refreshInterval)} + value={usePrettyInterval(Boolean(isPaused), refreshInterval)} {...rest} /> } @@ -124,7 +124,7 @@ export const EuiAutoRefreshButton: FunctionComponent const autoRefeshLabelOn = useEuiI18n( 'euiAutoRefresh.buttonLabelOn', 'Auto refresh is on and set to {prettyInterval}', - { prettyInterval: prettyInterval(Boolean(isPaused), refreshInterval) } + { prettyInterval: usePrettyInterval(Boolean(isPaused), refreshInterval) } ); return ( @@ -140,7 +140,7 @@ export const EuiAutoRefreshButton: FunctionComponent isDisabled={isDisabled} {...rest} > - {prettyInterval(Boolean(isPaused), refreshInterval, shortHand)} + {usePrettyInterval(Boolean(isPaused), refreshInterval, shortHand)} } isOpen={isPopoverOpen} diff --git a/src/components/date_picker/super_date_picker/pretty_interval.test.ts b/src/components/date_picker/super_date_picker/pretty_interval.test.ts index e15004af616d..364768a58329 100644 --- a/src/components/date_picker/super_date_picker/pretty_interval.test.ts +++ b/src/components/date_picker/super_date_picker/pretty_interval.test.ts @@ -6,40 +6,77 @@ * Side Public License, v 1. */ -import { prettyInterval } from './pretty_interval'; +import { testCustomHook } from '../../../test/test_custom_hook.test_helper'; + +import { usePrettyInterval } from './pretty_interval'; const IS_NOT_PAUSED = false; const IS_PAUSED = true; const SHORT_HAND = true; -test('Off', () => { - expect(prettyInterval(IS_NOT_PAUSED, 0)).toBe('Off'); - expect(prettyInterval(IS_PAUSED, 1000)).toBe('Off'); -}); +describe('usePrettyInterval', () => { + test('off', () => { + expect( + testCustomHook(() => usePrettyInterval(IS_NOT_PAUSED, 0)).return + ).toBe('Off'); + expect( + testCustomHook(() => usePrettyInterval(IS_PAUSED, 1000)).return + ).toBe('Off'); + }); -test('seconds', () => { - expect(prettyInterval(IS_NOT_PAUSED, 1000)).toBe('1 second'); - expect(prettyInterval(IS_NOT_PAUSED, 15000)).toBe('15 seconds'); -}); + test('seconds', () => { + expect( + testCustomHook(() => usePrettyInterval(IS_NOT_PAUSED, 1000)).return + ).toBe('1 second'); + expect( + testCustomHook(() => usePrettyInterval(IS_NOT_PAUSED, 15000)).return + ).toBe('15 seconds'); + expect( + testCustomHook(() => usePrettyInterval(IS_NOT_PAUSED, 15000, SHORT_HAND)) + .return + ).toBe('15 s'); + }); -test('minutes', () => { - expect(prettyInterval(IS_NOT_PAUSED, 60000)).toBe('1 minute'); - expect(prettyInterval(IS_NOT_PAUSED, 1800000)).toBe('30 minutes'); -}); + test('minutes', () => { + expect( + testCustomHook(() => usePrettyInterval(IS_NOT_PAUSED, 60000)).return + ).toBe('1 minute'); + expect( + testCustomHook(() => usePrettyInterval(IS_NOT_PAUSED, 1800000)).return + ).toBe('30 minutes'); + expect( + testCustomHook(() => + usePrettyInterval(IS_NOT_PAUSED, 1800000, SHORT_HAND) + ).return + ).toBe('30 m'); + }); -test('hours', () => { - expect(prettyInterval(IS_NOT_PAUSED, 3600000)).toBe('1 hour'); - expect(prettyInterval(IS_NOT_PAUSED, 43200000)).toBe('12 hours'); -}); - -test('days', () => { - expect(prettyInterval(IS_NOT_PAUSED, 86400000)).toBe('1 day'); - expect(prettyInterval(IS_NOT_PAUSED, 86400000 * 2)).toBe('2 days'); -}); + test('hours', () => { + expect( + testCustomHook(() => usePrettyInterval(IS_NOT_PAUSED, 3600000)).return + ).toBe('1 hour'); + expect( + testCustomHook(() => usePrettyInterval(IS_NOT_PAUSED, 43200000)).return + ).toBe('12 hours'); + expect( + testCustomHook(() => + usePrettyInterval(IS_NOT_PAUSED, 43200000, SHORT_HAND) + ).return + ).toBe('12 h'); + }); -test('shortHand', () => { - expect(prettyInterval(IS_NOT_PAUSED, 86400000, SHORT_HAND)).toBe('1 d'); - expect(prettyInterval(IS_NOT_PAUSED, 43200000, SHORT_HAND)).toBe('12 h'); - expect(prettyInterval(IS_NOT_PAUSED, 1800000, SHORT_HAND)).toBe('30 m'); - expect(prettyInterval(IS_NOT_PAUSED, 15000, SHORT_HAND)).toBe('15 s'); + test('days', () => { + expect( + testCustomHook(() => usePrettyInterval(IS_NOT_PAUSED, 86400000)).return + ).toBe('1 day'); + expect( + testCustomHook(() => usePrettyInterval(IS_NOT_PAUSED, 86400000 * 2)) + .return + ).toBe('2 days'); + expect( + testCustomHook(() => + usePrettyInterval(IS_NOT_PAUSED, 86400000, SHORT_HAND) + ).return + ).toBe('1 d'); + }); }); diff --git a/src/components/date_picker/super_date_picker/pretty_interval.ts b/src/components/date_picker/super_date_picker/pretty_interval.ts index 60f6b2de9de7..b5e28d6eeab0 100644 --- a/src/components/date_picker/super_date_picker/pretty_interval.ts +++ b/src/components/date_picker/super_date_picker/pretty_interval.ts @@ -6,38 +6,68 @@ * Side Public License, v 1. */ +import { useEuiI18n } from '../../i18n'; +import { useI18nTimeOptions } from './time_options'; + const MS_IN_SECOND = 1000; const MS_IN_MINUTE = 60 * MS_IN_SECOND; const MS_IN_HOUR = 60 * MS_IN_MINUTE; const MS_IN_DAY = 24 * MS_IN_HOUR; -export const prettyInterval = ( +type IntervalUnitId = 's' | 'm' | 'h' | 'd'; + +export const usePrettyInterval = ( isPaused: boolean, intervalInMs: number, shortHand: boolean = false ) => { - let units: string; - if (isPaused || intervalInMs === 0) { - return 'Off'; - } else if (intervalInMs < MS_IN_MINUTE) { - const intervalInSeconds = Math.round(intervalInMs / MS_IN_SECOND); - if (shortHand) return `${intervalInSeconds} s`; - units = intervalInSeconds > 1 ? 'seconds' : 'second'; - return `${intervalInSeconds} ${units}`; + let prettyInterval = ''; + let interval: number; + let unitId: IntervalUnitId; + + if (intervalInMs < MS_IN_MINUTE) { + interval = Math.round(intervalInMs / MS_IN_SECOND); + unitId = 's'; } else if (intervalInMs < MS_IN_HOUR) { - const intervalInMinutes = Math.round(intervalInMs / MS_IN_MINUTE); - if (shortHand) return `${intervalInMinutes} m`; - units = intervalInMinutes > 1 ? 'minutes' : 'minute'; - return `${intervalInMinutes} ${units}`; + interval = Math.round(intervalInMs / MS_IN_MINUTE); + unitId = 'm'; } else if (intervalInMs < MS_IN_DAY) { - const intervalInHours = Math.round(intervalInMs / MS_IN_HOUR); - if (shortHand) return `${intervalInHours} h`; - units = intervalInHours > 1 ? 'hours' : 'hour'; - return `${intervalInHours} ${units}`; + interval = Math.round(intervalInMs / MS_IN_HOUR); + unitId = 'h'; + } else { + interval = Math.round(intervalInMs / MS_IN_DAY); + unitId = 'd'; + } + prettyInterval = useI18nUnits(interval, unitId, shortHand); + + const off = useEuiI18n('euiPrettyInterval.off', 'Off'); + if (isPaused || intervalInMs === 0) { + prettyInterval = off; + } + + return prettyInterval; +}; + +const useI18nUnits = ( + interval: number, + unitId: IntervalUnitId, + shortHand: boolean +) => { + const { + timeUnits, + timeUnitsPlural, + refreshUnitsShorthand, + } = useI18nTimeOptions(); + + let units = ''; + + if (shortHand) { + units = refreshUnitsShorthand[unitId]; + } else if (interval > 1) { + units = timeUnitsPlural[unitId]; + } else { + units = timeUnits[unitId]; } - const intervalInDays = Math.round(intervalInMs / MS_IN_DAY); - if (shortHand) return `${intervalInDays} d`; - units = intervalInDays > 1 ? 'days' : 'day'; - return `${intervalInDays} ${units}`; + return `${interval} ${units}`; }; diff --git a/src/components/date_picker/super_date_picker/time_options.tsx b/src/components/date_picker/super_date_picker/time_options.tsx index f467f5f428f6..b71cbf1e8ca1 100644 --- a/src/components/date_picker/super_date_picker/time_options.tsx +++ b/src/components/date_picker/super_date_picker/time_options.tsx @@ -27,6 +27,7 @@ export type TimeOptions = { timeUnitsPlural: { [id in TimeUnitId]: TimeUnitLabelPlural }; relativeOptions: RelativeOption[]; refreshUnitsOptions: RelativeOption[]; + refreshUnitsShorthand: { [id: string]: string }; }; export const useI18nTimeOptions = () => { @@ -129,6 +130,13 @@ export const useI18nTimeOptions = () => { ) .map((timeUnit) => ({ value: timeUnit, text: timeUnitsPlural[timeUnit] })); + const refreshUnitsShorthand = { + s: useEuiI18n('euiTimeOptions.secondsShorthand', 's'), + m: useEuiI18n('euiTimeOptions.monthsShorthand', 'm'), + h: useEuiI18n('euiTimeOptions.monthsShorthand', 'h'), + d: useEuiI18n('euiTimeOptions.daysShorthand', 'd'), + }; + return { timeTenseOptions, timeUnits, @@ -136,6 +144,7 @@ export const useI18nTimeOptions = () => { timeUnitsPlural, relativeOptions, refreshUnitsOptions, + refreshUnitsShorthand, }; }; From c08fc93f69bf77496f7d892680ed1e42889b0fb4 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 23 Mar 2022 11:41:52 -0700 Subject: [PATCH 12/29] i18n `Commonly used` ranges in quick select - This one was tricky and what necessitated the render prop wrapper, because commonlyUsedRanges uses it as a default prop - this was the most straightforward way I could think of to pass it as a fallback --- .../super_date_picker/pretty_duration.ts | 11 ----- .../super_date_picker/super_date_picker.tsx | 9 +++- .../super_date_picker/time_options.tsx | 46 +++++++++++++++++++ 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/components/date_picker/super_date_picker/pretty_duration.ts b/src/components/date_picker/super_date_picker/pretty_duration.ts index 29f496cd6d03..47e0aaa29906 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.ts +++ b/src/components/date_picker/super_date_picker/pretty_duration.ts @@ -15,17 +15,6 @@ import { DurationRange, TimeUnitId, ShortDate } from '../types'; const ISO_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; -export const commonDurationRanges: DurationRange[] = [ - { start: 'now/d', end: 'now/d', label: 'Today' }, - { start: 'now/w', end: 'now/w', label: 'This week' }, - { start: 'now/M', end: 'now/M', label: 'This month' }, - { start: 'now/y', end: 'now/y', label: 'This year' }, - { start: 'now-1d/d', end: 'now-1d/d', label: 'Yesterday' }, - { start: 'now/w', end: 'now', label: 'Week to date' }, - { start: 'now/M', end: 'now', label: 'Month to date' }, - { start: 'now/y', end: 'now', label: 'Year to date' }, -]; - function cantLookup(timeFrom: string, timeTo: string, dateFormat: string) { const displayFrom = formatTimeString(timeFrom, dateFormat); const displayTo = formatTimeString(timeTo, dateFormat, true); diff --git a/src/components/date_picker/super_date_picker/super_date_picker.tsx b/src/components/date_picker/super_date_picker/super_date_picker.tsx index cd9c150d69f2..6bd05f4cd7f9 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.tsx +++ b/src/components/date_picker/super_date_picker/super_date_picker.tsx @@ -196,7 +196,6 @@ export class EuiSuperDatePickerInternal extends Component< EuiSuperDatePickerState > { static defaultProps = { - commonlyUsedRanges: commonDurationRanges, dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', end: 'now', isAutoRefreshOnly: false, @@ -627,7 +626,13 @@ export class EuiSuperDatePickerInternal extends Component< export const EuiSuperDatePicker = (props: EuiSuperDatePickerProps) => ( {(timeOptions) => ( - + )} ); diff --git a/src/components/date_picker/super_date_picker/time_options.tsx b/src/components/date_picker/super_date_picker/time_options.tsx index b71cbf1e8ca1..2f154ff5873d 100644 --- a/src/components/date_picker/super_date_picker/time_options.tsx +++ b/src/components/date_picker/super_date_picker/time_options.tsx @@ -15,6 +15,7 @@ import { TimeUnitLabel, TimeUnitLabelPlural, RelativeOption, + DurationRange, } from '../types'; export const LAST = 'last'; @@ -28,6 +29,7 @@ export type TimeOptions = { relativeOptions: RelativeOption[]; refreshUnitsOptions: RelativeOption[]; refreshUnitsShorthand: { [id: string]: string }; + commonDurationRanges: DurationRange[]; }; export const useI18nTimeOptions = () => { @@ -137,6 +139,49 @@ export const useI18nTimeOptions = () => { d: useEuiI18n('euiTimeOptions.daysShorthand', 'd'), }; + const commonDurationRanges = [ + { + start: 'now/d', + end: 'now/d', + label: useEuiI18n('euiTimeOptions.today', 'Today'), + }, + { + start: 'now/w', + end: 'now/w', + label: useEuiI18n('euiTimeOptions.thisWeek', 'This week'), + }, + { + start: 'now/M', + end: 'now/M', + label: useEuiI18n('euiTimeOptions.thisMonth', 'This month'), + }, + { + start: 'now/y', + end: 'now/y', + label: useEuiI18n('euiTimeOptions.thisYear', 'This year'), + }, + { + start: 'now-1d/d', + end: 'now-1d/d', + label: useEuiI18n('euiTimeOptions.yesterday', 'Yesterday'), + }, + { + start: 'now/w', + end: 'now', + label: useEuiI18n('euiTimeOptions.weekToDate', 'Week to date'), + }, + { + start: 'now/M', + end: 'now', + label: useEuiI18n('euiTimeOptions.monthToDate', 'Month to date'), + }, + { + start: 'now/y', + end: 'now', + label: useEuiI18n('euiTimeOptions.yearToDate', 'Year to date'), + }, + ]; + return { timeTenseOptions, timeUnits, @@ -145,6 +190,7 @@ export const useI18nTimeOptions = () => { relativeOptions, refreshUnitsOptions, refreshUnitsShorthand, + commonDurationRanges, }; }; From 3d9e9e70ad8c4f2a31d9bc32f726e2cd0cb2bbdd Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 23 Mar 2022 12:10:31 -0700 Subject: [PATCH 13/29] Convert `prettyDuration` to hook/component - cannot be a vanilla JS util any longer if it's using i18n (which requires React context) - a hook+component should covert both cases where a string is required vs rendering JSX - reorder utils slightly - update formatTimeString to hook as well to render i18n strings --- .../date_popover/date_popover_button.tsx | 11 +- .../date_picker/super_date_picker/index.ts | 2 +- ...ation.test.ts => pretty_duration.test.tsx} | 68 +++++-- .../super_date_picker/pretty_duration.ts | 116 ----------- .../super_date_picker/pretty_duration.tsx | 183 ++++++++++++++++++ .../quick_select_popover/recently_used.tsx | 9 +- .../super_date_picker/super_date_picker.tsx | 21 +- 7 files changed, 264 insertions(+), 146 deletions(-) rename src/components/date_picker/super_date_picker/{pretty_duration.test.ts => pretty_duration.test.tsx} (57%) delete mode 100644 src/components/date_picker/super_date_picker/pretty_duration.ts create mode 100644 src/components/date_picker/super_date_picker/pretty_duration.tsx diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx index b987cc29664b..a12b78b630aa 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx @@ -18,7 +18,7 @@ import { useEuiI18n } from '../../../i18n'; import { EuiPopover, EuiPopoverProps } from '../../../popover'; import { TimeOptions } from '../time_options'; -import { formatTimeString } from '../pretty_duration'; +import { useFormatTimeString } from '../pretty_duration'; import { EuiDatePopoverContent, EuiDatePopoverContentProps, @@ -42,6 +42,7 @@ export interface EuiDatePopoverButtonProps { value: string; utcOffset?: number; compressed?: boolean; + timeOptions: TimeOptions; } export const EuiDatePopoverButton: FunctionComponent = ( @@ -64,6 +65,7 @@ export const EuiDatePopoverButton: FunctionComponent onPopoverToggle, onPopoverClose, compressed, + timeOptions, ...rest } = props; @@ -79,7 +81,12 @@ export const EuiDatePopoverButton: FunctionComponent }, ]); - const formattedValue = formatTimeString(value, dateFormat, roundUp, locale); + const formattedValue = useFormatTimeString( + value, + dateFormat, + roundUp, + locale + ); let title = formattedValue; const invalidTitle = useEuiI18n( diff --git a/src/components/date_picker/super_date_picker/index.ts b/src/components/date_picker/super_date_picker/index.ts index 397e96c4a780..a9c9fefb3f05 100644 --- a/src/components/date_picker/super_date_picker/index.ts +++ b/src/components/date_picker/super_date_picker/index.ts @@ -20,4 +20,4 @@ export { EuiSuperDatePicker } from './super_date_picker'; export type { EuiSuperUpdateButtonProps } from './super_update_button'; export { EuiSuperUpdateButton } from './super_update_button'; -export { prettyDuration, commonDurationRanges } from './pretty_duration'; +export { PrettyDuration, usePrettyDuration } from './pretty_duration'; diff --git a/src/components/date_picker/super_date_picker/pretty_duration.test.ts b/src/components/date_picker/super_date_picker/pretty_duration.test.tsx similarity index 57% rename from src/components/date_picker/super_date_picker/pretty_duration.test.ts rename to src/components/date_picker/super_date_picker/pretty_duration.test.tsx index cd91fbbce75f..a05b3d0dc5ac 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.test.ts +++ b/src/components/date_picker/super_date_picker/pretty_duration.test.tsx @@ -6,7 +6,15 @@ * Side Public License, v 1. */ -import { prettyDuration, showPrettyDuration } from './pretty_duration'; +import React from 'react'; +import { shallow } from 'enzyme'; +import { testCustomHook } from '../../../test/test_custom_hook.test_helper'; + +import { + usePrettyDuration, + PrettyDuration, + showPrettyDuration, +} from './pretty_duration'; const dateFormat = 'MMMM Do YYYY, HH:mm:ss.SSS'; const quickRanges = [ @@ -17,45 +25,73 @@ const quickRanges = [ }, ]; -describe('prettyDuration', () => { +describe('usePrettyDuration', () => { test('quick range', () => { const timeFrom = 'now-15m'; const timeTo = 'now'; - expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe( - 'quick range 15 minutes custom display' - ); + expect( + testCustomHook(() => + usePrettyDuration({ timeFrom, timeTo, quickRanges, dateFormat }) + ).return + ).toBe('quick range 15 minutes custom display'); }); test('last', () => { const timeFrom = 'now-16m'; const timeTo = 'now'; - expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe( - 'Last 16 minutes' - ); + expect( + testCustomHook(() => + usePrettyDuration({ timeFrom, timeTo, quickRanges, dateFormat }) + ).return + ).toBe('Last 16 minutes'); }); test('last that is rounded', () => { const timeFrom = 'now-1M/w'; const timeTo = 'now'; - expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe( - 'Last 1 month rounded to the week' - ); + expect( + testCustomHook(() => + usePrettyDuration({ timeFrom, timeTo, quickRanges, dateFormat }) + ).return + ).toBe('Last 1 month rounded to the week'); }); test('next', () => { const timeFrom = 'now'; const timeTo = 'now+16m'; - expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe( - 'Next 16 minutes' - ); + expect( + testCustomHook(() => + usePrettyDuration({ timeFrom, timeTo, quickRanges, dateFormat }) + ).return + ).toBe('Next 16 minutes'); }); test('from is in past', () => { const timeFrom = 'now-17m'; const timeTo = 'now-15m'; - expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe( - '~ 17 minutes ago to ~ 15 minutes ago' + expect( + testCustomHook(() => + usePrettyDuration({ timeFrom, timeTo, quickRanges, dateFormat }) + ).return + ).toBe('~ 17 minutes ago to ~ 15 minutes ago'); + }); +}); + +describe('PrettyDuration', () => { + it('renders the returned string from usePrettyDuration', () => { + const component = shallow( + ); + expect(component).toMatchInlineSnapshot(` + + Next 15 minutes + + `); }); }); diff --git a/src/components/date_picker/super_date_picker/pretty_duration.ts b/src/components/date_picker/super_date_picker/pretty_duration.ts deleted file mode 100644 index 47e0aaa29906..000000000000 --- a/src/components/date_picker/super_date_picker/pretty_duration.ts +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import dateMath from '@elastic/datemath'; -import moment, { LocaleSpecifier } from 'moment'; // eslint-disable-line import/named -import { timeUnits, timeUnitsPlural } from './time_units'; -import { getDateMode, DATE_MODES } from './date_modes'; -import { parseRelativeParts } from './relative_utils'; -import { DurationRange, TimeUnitId, ShortDate } from '../types'; - -const ISO_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; - -function cantLookup(timeFrom: string, timeTo: string, dateFormat: string) { - const displayFrom = formatTimeString(timeFrom, dateFormat); - const displayTo = formatTimeString(timeTo, dateFormat, true); - return `${displayFrom} to ${displayTo}`; -} - -function isRelativeToNow(timeFrom: ShortDate, timeTo: ShortDate) { - const fromDateMode = getDateMode(timeFrom); - const toDateMode = getDateMode(timeTo); - const isLast = - fromDateMode === DATE_MODES.RELATIVE && toDateMode === DATE_MODES.NOW; - const isNext = - fromDateMode === DATE_MODES.NOW && toDateMode === DATE_MODES.RELATIVE; - return isLast || isNext; -} - -export function formatTimeString( - timeString: string, - dateFormat: string, - roundUp = false, - locale: LocaleSpecifier = 'en' -): string { - const timeAsMoment = moment(timeString, ISO_FORMAT, true); - if (timeAsMoment.isValid()) { - return timeAsMoment.locale(locale).format(dateFormat); - } - - if (timeString === 'now') { - return 'now'; - } - - const tryParse = dateMath.parse(timeString, { roundUp: roundUp }); - if (!moment(tryParse).isValid()) { - return 'Invalid Date'; - } - - if (moment.isMoment(tryParse)) { - return `~ ${tryParse.locale(locale).fromNow()}`; - } - - return timeString; -} - -export function prettyDuration( - timeFrom: ShortDate, - timeTo: ShortDate, - quickRanges: DurationRange[] = [], - dateFormat: string -) { - const matchingQuickRange = quickRanges.find( - ({ start: quickFrom, end: quickTo }) => { - return timeFrom === quickFrom && timeTo === quickTo; - } - ); - if (matchingQuickRange && matchingQuickRange.label) { - return matchingQuickRange.label; - } - - if (isRelativeToNow(timeFrom, timeTo)) { - let timeTense; - let relativeParts; - if (getDateMode(timeTo) === DATE_MODES.NOW) { - timeTense = 'Last'; - relativeParts = parseRelativeParts(timeFrom); - } else { - timeTense = 'Next'; - relativeParts = parseRelativeParts(timeTo); - } - const countTimeUnit = relativeParts.unit.substring(0, 1) as TimeUnitId; - const countTimeUnitFullName = - relativeParts.count > 1 - ? timeUnitsPlural[countTimeUnit] - : timeUnits[countTimeUnit]; - let text = `${timeTense} ${relativeParts.count} ${countTimeUnitFullName}`; - if (relativeParts.round && relativeParts.roundUnit) { - text += ` rounded to the ${timeUnits[relativeParts.roundUnit]}`; - } - return text; - } - - return cantLookup(timeFrom, timeTo, dateFormat); -} - -export function showPrettyDuration( - timeFrom: ShortDate, - timeTo: ShortDate, - quickRanges: DurationRange[] = [] -) { - const matchingQuickRange = quickRanges.find( - ({ start: quickFrom, end: quickTo }) => { - return timeFrom === quickFrom && timeTo === quickTo; - } - ); - if (matchingQuickRange) { - return true; - } - - return isRelativeToNow(timeFrom, timeTo); -} diff --git a/src/components/date_picker/super_date_picker/pretty_duration.tsx b/src/components/date_picker/super_date_picker/pretty_duration.tsx new file mode 100644 index 000000000000..3d53a3e0dc78 --- /dev/null +++ b/src/components/date_picker/super_date_picker/pretty_duration.tsx @@ -0,0 +1,183 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import dateMath from '@elastic/datemath'; +import moment, { LocaleSpecifier } from 'moment'; // eslint-disable-line import/named +import { useEuiI18n } from '../../i18n'; +import { getDateMode, DATE_MODES } from './date_modes'; +import { parseRelativeParts } from './relative_utils'; +import { useI18nTimeOptions } from './time_options'; +import { DurationRange, TimeUnitId, ShortDate, RelativeParts } from '../types'; + +const ISO_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; + +export const useFormatTimeString = ( + timeString: string, + dateFormat: string, + roundUp = false, + locale: LocaleSpecifier = 'en' +): string => { + // i18n'd strings + const nowDisplay = useEuiI18n('euiPrettyDuration.now', 'now'); + const invalidDateDisplay = useEuiI18n( + 'euiPrettyDuration.invalid', + 'Invalid date' + ); + + const timeAsMoment = moment(timeString, ISO_FORMAT, true); + if (timeAsMoment.isValid()) { + return timeAsMoment.locale(locale).format(dateFormat); + } + + if (timeString === 'now') { + return nowDisplay; + } + + const tryParse = dateMath.parse(timeString, { roundUp: roundUp }); + if (!moment(tryParse).isValid()) { + return invalidDateDisplay; + } + + if (moment.isMoment(tryParse)) { + return `~ ${tryParse.locale(locale).fromNow()}`; + } + + return timeString; +}; + +/** + * Pretty duration hook+component + */ + +interface PrettyDurationProps { + timeFrom: ShortDate; + timeTo: ShortDate; + quickRanges?: DurationRange[]; + dateFormat: string; +} + +export const usePrettyDuration = ({ + timeFrom, + timeTo, + quickRanges, + dateFormat, +}: PrettyDurationProps) => { + let prettyDuration: string = ''; + + const timeOptions = useI18nTimeOptions(); + const { + timeUnits, + timeUnitsPlural, + timeTenseOptions, + commonDurationRanges, + } = timeOptions; + + const matchingQuickRange = hasRangeMatch( + timeFrom, + timeTo, + quickRanges || commonDurationRanges + ); + if (matchingQuickRange && matchingQuickRange.label) { + prettyDuration = matchingQuickRange.label; + } + + let relativeDuration = ''; + let relativeParts = {} as RelativeParts; + if (isRelativeToNow(timeFrom, timeTo)) { + let timeTense; + if (getDateMode(timeTo) === DATE_MODES.NOW) { + timeTense = timeTenseOptions[0].text; // Last + relativeParts = parseRelativeParts(timeFrom); + } else { + timeTense = timeTenseOptions[1].text; // Next + relativeParts = parseRelativeParts(timeTo); + } + + const countTimeUnit = relativeParts.unit.substring(0, 1) as TimeUnitId; + const countTimeUnitFullName = + relativeParts.count > 1 + ? timeUnitsPlural[countTimeUnit] + : timeUnits[countTimeUnit]; + + relativeDuration = `${timeTense} ${relativeParts.count} ${countTimeUnitFullName}`; + } + const roundedRelativeDuration = useEuiI18n( + 'euiPrettyDuration.roundedRelativeDuration', + '{time} rounded to the {unit}', + { time: relativeDuration, unit: timeUnits[relativeParts.roundUnit!] } + ); + if (relativeParts.round && relativeParts.roundUnit) { + relativeDuration = roundedRelativeDuration; + } + if (!prettyDuration) { + prettyDuration = relativeDuration; + } + + // Can't look up the returned value, display a fallback text + const displayFrom = useFormatTimeString(timeFrom, dateFormat); + const displayTo = useFormatTimeString(timeTo, dateFormat, true); + const fallbackDuration = useEuiI18n( + 'euiPrettyDuration.fallbackDuration', + '{displayFrom} to {displayTo}', + { displayFrom, displayTo } + ); + if (!prettyDuration) { + prettyDuration = fallbackDuration; + } + + return prettyDuration; +}; + +export const PrettyDuration: React.FC = ({ + timeFrom, + timeTo, + quickRanges, + dateFormat, +}) => { + const prettyDuration = usePrettyDuration({ + timeFrom, + timeTo, + quickRanges, + dateFormat, + }); + return <>{prettyDuration}; +}; + +/** + * Non-hook utils + */ + +const hasRangeMatch = ( + timeFrom: ShortDate, + timeTo: ShortDate, + ranges: DurationRange[] +) => { + return ranges.find(({ start, end }) => timeFrom === start && timeTo === end); +}; + +const isRelativeToNow = (timeFrom: ShortDate, timeTo: ShortDate): boolean => { + const fromDateMode = getDateMode(timeFrom); + const toDateMode = getDateMode(timeTo); + const isLast = + fromDateMode === DATE_MODES.RELATIVE && toDateMode === DATE_MODES.NOW; + const isNext = + fromDateMode === DATE_MODES.NOW && toDateMode === DATE_MODES.RELATIVE; + return isLast || isNext; +}; + +export const showPrettyDuration = ( + timeFrom: ShortDate, + timeTo: ShortDate, + quickRanges: DurationRange[] +): boolean => { + if (hasRangeMatch(timeFrom, timeTo, quickRanges)) { + return true; + } + return isRelativeToNow(timeFrom, timeTo); +}; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/recently_used.tsx b/src/components/date_picker/super_date_picker/quick_select_popover/recently_used.tsx index 6a12a7222f65..2855dd3b57da 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/recently_used.tsx +++ b/src/components/date_picker/super_date_picker/quick_select_popover/recently_used.tsx @@ -7,7 +7,7 @@ */ import React, { FunctionComponent } from 'react'; -import { prettyDuration } from '../pretty_duration'; +import { PrettyDuration } from '../pretty_duration'; import { EuiI18n } from '../../../i18n'; import { useGeneratedHtmlId } from '../../../../services'; @@ -44,7 +44,12 @@ export const EuiRecentlyUsed: FunctionComponent = ({ key={`${start}-${end}`} > - {prettyDuration(start, end, commonlyUsedRanges, dateFormat)} + ); diff --git a/src/components/date_picker/super_date_picker/super_date_picker.tsx b/src/components/date_picker/super_date_picker/super_date_picker.tsx index 6bd05f4cd7f9..f07fd91132e5 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.tsx +++ b/src/components/date_picker/super_date_picker/super_date_picker.tsx @@ -27,11 +27,7 @@ import { } from '../types'; import { TimeOptions, RenderI18nTimeOptions } from './time_options'; -import { - prettyDuration, - showPrettyDuration, - commonDurationRanges, -} from './pretty_duration'; +import { PrettyDuration, showPrettyDuration } from './pretty_duration'; import { AsyncInterval } from './async_interval'; import { @@ -47,8 +43,6 @@ import { EuiAutoRefreshButton, } from '../auto_refresh/auto_refresh'; -export { prettyDuration, commonDurationRanges }; - export interface OnTimeChangeProps extends DurationRange { isInvalid: boolean; isQuickSelection: boolean; @@ -315,7 +309,11 @@ export class EuiSuperDatePickerInternal extends Component< applyQuickTime: ApplyTime = ({ start, end }) => { this.setState({ - showPrettyDuration: showPrettyDuration(start, end, commonDurationRanges), + showPrettyDuration: showPrettyDuration( + start, + end, + this.props.commonlyUsedRanges + ), }); this.props.onTimeChange({ start, @@ -418,7 +416,12 @@ export class EuiSuperDatePickerInternal extends Component< disabled={isDisabled} onClick={this.hidePrettyDuration} > - {prettyDuration(start, end, commonlyUsedRanges, dateFormat)} + ); From 94ebca30856e3642dbcc22b59146fabc91e1c451 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 23 Mar 2022 12:18:48 -0700 Subject: [PATCH 14/29] Add time_options unit tests --- .../super_date_picker/time_options.test.tsx | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 src/components/date_picker/super_date_picker/time_options.test.tsx diff --git a/src/components/date_picker/super_date_picker/time_options.test.tsx b/src/components/date_picker/super_date_picker/time_options.test.tsx new file mode 100644 index 000000000000..a57abc6dc076 --- /dev/null +++ b/src/components/date_picker/super_date_picker/time_options.test.tsx @@ -0,0 +1,218 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import { testCustomHook } from '../../../test/test_custom_hook.test_helper'; + +import { useI18nTimeOptions, RenderI18nTimeOptions } from './time_options'; + +describe('useI18nTimeOptions', () => { + it('returns a series of time options constants/arrays/objects with i18n strings', () => { + const { return: timeOptions } = testCustomHook(useI18nTimeOptions); + + expect(timeOptions).toMatchInlineSnapshot(` + Object { + "commonDurationRanges": Array [ + Object { + "end": "now/d", + "label": "Today", + "start": "now/d", + }, + Object { + "end": "now/w", + "label": "This week", + "start": "now/w", + }, + Object { + "end": "now/M", + "label": "This month", + "start": "now/M", + }, + Object { + "end": "now/y", + "label": "This year", + "start": "now/y", + }, + Object { + "end": "now-1d/d", + "label": "Yesterday", + "start": "now-1d/d", + }, + Object { + "end": "now", + "label": "Week to date", + "start": "now/w", + }, + Object { + "end": "now", + "label": "Month to date", + "start": "now/M", + }, + Object { + "end": "now", + "label": "Year to date", + "start": "now/y", + }, + ], + "refreshUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + ], + "refreshUnitsShorthand": Object { + "d": "d", + "h": "h", + "m": "m", + "s": "s", + }, + "relativeOptions": Array [ + Object { + "text": "Seconds ago", + "value": "s", + }, + Object { + "text": "Minutes ago", + "value": "m", + }, + Object { + "text": "Hours ago", + "value": "h", + }, + Object { + "text": "Days ago", + "value": "d", + }, + Object { + "text": "Weeks ago", + "value": "w", + }, + Object { + "text": "Months ago", + "value": "M", + }, + Object { + "text": "Years ago", + "value": "y", + }, + Object { + "text": "Seconds from now", + "value": "s+", + }, + Object { + "text": "Minutes from now", + "value": "m+", + }, + Object { + "text": "Hours from now", + "value": "h+", + }, + Object { + "text": "Days from now", + "value": "d+", + }, + Object { + "text": "Weeks from now", + "value": "w+", + }, + Object { + "text": "Months from now", + "value": "M+", + }, + Object { + "text": "Years from now", + "value": "y+", + }, + ], + "timeTenseOptions": Array [ + Object { + "text": "Last", + "value": "last", + }, + Object { + "text": "Next", + "value": "next", + }, + ], + "timeUnits": Object { + "M": "month", + "d": "day", + "h": "hour", + "m": "minute", + "s": "second", + "w": "week", + "y": "year", + }, + "timeUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + Object { + "text": "days", + "value": "d", + }, + Object { + "text": "weeks", + "value": "w", + }, + Object { + "text": "months", + "value": "M", + }, + Object { + "text": "years", + "value": "y", + }, + ], + "timeUnitsPlural": Object { + "M": "months", + "d": "days", + "h": "hours", + "m": "minutes", + "s": "seconds", + "w": "weeks", + "y": "years", + }, + } + `); + }); +}); + +describe('RenderI18nTimeOptions', () => { + it('is a render function that passes the underlying children timeOptions as an arg', () => { + const component = shallow( + + {({ timeUnits }) => <>{timeUnits.m}} + + ); + + expect(component).toMatchInlineSnapshot(` + + minute + + `); + }); +}); From 02198dded0e43f997de1c2abc56b700f0fb6b05d Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 23 Mar 2022 12:20:17 -0700 Subject: [PATCH 15/29] Update various snapshot/unit tests with timeOptions waterfalling --- .../super_date_picker.test.tsx.snap | 1806 ++++++++++++++++- .../quick_select_popover.test.tsx.snap | 182 ++ .../quick_select.test.tsx | 32 +- .../quick_select_popover.test.tsx | 12 +- 4 files changed, 2012 insertions(+), 20 deletions(-) diff --git a/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap b/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap index 936de55269a3..534e4d81befc 100644 --- a/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap +++ b/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap @@ -64,6 +64,188 @@ exports[`EuiSuperDatePicker is rendered 1`] = ` recentlyUsedRanges={Array []} refreshInterval={1000} start="now-15m" + timeOptions={ + Object { + "commonDurationRanges": Array [ + Object { + "end": "now/d", + "label": "Today", + "start": "now/d", + }, + Object { + "end": "now/w", + "label": "This week", + "start": "now/w", + }, + Object { + "end": "now/M", + "label": "This month", + "start": "now/M", + }, + Object { + "end": "now/y", + "label": "This year", + "start": "now/y", + }, + Object { + "end": "now-1d/d", + "label": "Yesterday", + "start": "now-1d/d", + }, + Object { + "end": "now", + "label": "Week to date", + "start": "now/w", + }, + Object { + "end": "now", + "label": "Month to date", + "start": "now/M", + }, + Object { + "end": "now", + "label": "Year to date", + "start": "now/y", + }, + ], + "refreshUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + ], + "refreshUnitsShorthand": Object { + "d": "d", + "h": "h", + "m": "m", + "s": "s", + }, + "relativeOptions": Array [ + Object { + "text": "Seconds ago", + "value": "s", + }, + Object { + "text": "Minutes ago", + "value": "m", + }, + Object { + "text": "Hours ago", + "value": "h", + }, + Object { + "text": "Days ago", + "value": "d", + }, + Object { + "text": "Weeks ago", + "value": "w", + }, + Object { + "text": "Months ago", + "value": "M", + }, + Object { + "text": "Years ago", + "value": "y", + }, + Object { + "text": "Seconds from now", + "value": "s+", + }, + Object { + "text": "Minutes from now", + "value": "m+", + }, + Object { + "text": "Hours from now", + "value": "h+", + }, + Object { + "text": "Days from now", + "value": "d+", + }, + Object { + "text": "Weeks from now", + "value": "w+", + }, + Object { + "text": "Months from now", + "value": "M+", + }, + Object { + "text": "Years from now", + "value": "y+", + }, + ], + "timeTenseOptions": Array [ + Object { + "text": "Last", + "value": "last", + }, + Object { + "text": "Next", + "value": "next", + }, + ], + "timeUnits": Object { + "M": "month", + "d": "day", + "h": "hour", + "m": "minute", + "s": "second", + "w": "week", + "y": "year", + }, + "timeUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + Object { + "text": "days", + "value": "d", + }, + Object { + "text": "weeks", + "value": "w", + }, + Object { + "text": "months", + "value": "M", + }, + Object { + "text": "years", + "value": "y", + }, + ], + "timeUnitsPlural": Object { + "M": "months", + "d": "days", + "h": "hours", + "m": "minutes", + "s": "seconds", + "w": "weeks", + "y": "years", + }, + } + } /> } > @@ -80,7 +262,55 @@ exports[`EuiSuperDatePicker is rendered 1`] = ` disabled={false} onClick={[Function]} > - Last 15 minutes + @@ -174,6 +404,188 @@ exports[`EuiSuperDatePicker props accepts data-test-subj and passes to EuiFormCo recentlyUsedRanges={Array []} refreshInterval={1000} start="now-15m" + timeOptions={ + Object { + "commonDurationRanges": Array [ + Object { + "end": "now/d", + "label": "Today", + "start": "now/d", + }, + Object { + "end": "now/w", + "label": "This week", + "start": "now/w", + }, + Object { + "end": "now/M", + "label": "This month", + "start": "now/M", + }, + Object { + "end": "now/y", + "label": "This year", + "start": "now/y", + }, + Object { + "end": "now-1d/d", + "label": "Yesterday", + "start": "now-1d/d", + }, + Object { + "end": "now", + "label": "Week to date", + "start": "now/w", + }, + Object { + "end": "now", + "label": "Month to date", + "start": "now/M", + }, + Object { + "end": "now", + "label": "Year to date", + "start": "now/y", + }, + ], + "refreshUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + ], + "refreshUnitsShorthand": Object { + "d": "d", + "h": "h", + "m": "m", + "s": "s", + }, + "relativeOptions": Array [ + Object { + "text": "Seconds ago", + "value": "s", + }, + Object { + "text": "Minutes ago", + "value": "m", + }, + Object { + "text": "Hours ago", + "value": "h", + }, + Object { + "text": "Days ago", + "value": "d", + }, + Object { + "text": "Weeks ago", + "value": "w", + }, + Object { + "text": "Months ago", + "value": "M", + }, + Object { + "text": "Years ago", + "value": "y", + }, + Object { + "text": "Seconds from now", + "value": "s+", + }, + Object { + "text": "Minutes from now", + "value": "m+", + }, + Object { + "text": "Hours from now", + "value": "h+", + }, + Object { + "text": "Days from now", + "value": "d+", + }, + Object { + "text": "Weeks from now", + "value": "w+", + }, + Object { + "text": "Months from now", + "value": "M+", + }, + Object { + "text": "Years from now", + "value": "y+", + }, + ], + "timeTenseOptions": Array [ + Object { + "text": "Last", + "value": "last", + }, + Object { + "text": "Next", + "value": "next", + }, + ], + "timeUnits": Object { + "M": "month", + "d": "day", + "h": "hour", + "m": "minute", + "s": "second", + "w": "week", + "y": "year", + }, + "timeUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + Object { + "text": "days", + "value": "d", + }, + Object { + "text": "weeks", + "value": "w", + }, + Object { + "text": "months", + "value": "M", + }, + Object { + "text": "years", + "value": "y", + }, + ], + "timeUnitsPlural": Object { + "M": "months", + "d": "days", + "h": "hours", + "m": "minutes", + "s": "seconds", + "w": "weeks", + "y": "years", + }, + } + } /> } > @@ -190,7 +602,55 @@ exports[`EuiSuperDatePicker props accepts data-test-subj and passes to EuiFormCo disabled={false} onClick={[Function]} > - Last 15 minutes + @@ -284,6 +744,188 @@ exports[`EuiSuperDatePicker props compressed is rendered 1`] = ` recentlyUsedRanges={Array []} refreshInterval={1000} start="now-15m" + timeOptions={ + Object { + "commonDurationRanges": Array [ + Object { + "end": "now/d", + "label": "Today", + "start": "now/d", + }, + Object { + "end": "now/w", + "label": "This week", + "start": "now/w", + }, + Object { + "end": "now/M", + "label": "This month", + "start": "now/M", + }, + Object { + "end": "now/y", + "label": "This year", + "start": "now/y", + }, + Object { + "end": "now-1d/d", + "label": "Yesterday", + "start": "now-1d/d", + }, + Object { + "end": "now", + "label": "Week to date", + "start": "now/w", + }, + Object { + "end": "now", + "label": "Month to date", + "start": "now/M", + }, + Object { + "end": "now", + "label": "Year to date", + "start": "now/y", + }, + ], + "refreshUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + ], + "refreshUnitsShorthand": Object { + "d": "d", + "h": "h", + "m": "m", + "s": "s", + }, + "relativeOptions": Array [ + Object { + "text": "Seconds ago", + "value": "s", + }, + Object { + "text": "Minutes ago", + "value": "m", + }, + Object { + "text": "Hours ago", + "value": "h", + }, + Object { + "text": "Days ago", + "value": "d", + }, + Object { + "text": "Weeks ago", + "value": "w", + }, + Object { + "text": "Months ago", + "value": "M", + }, + Object { + "text": "Years ago", + "value": "y", + }, + Object { + "text": "Seconds from now", + "value": "s+", + }, + Object { + "text": "Minutes from now", + "value": "m+", + }, + Object { + "text": "Hours from now", + "value": "h+", + }, + Object { + "text": "Days from now", + "value": "d+", + }, + Object { + "text": "Weeks from now", + "value": "w+", + }, + Object { + "text": "Months from now", + "value": "M+", + }, + Object { + "text": "Years from now", + "value": "y+", + }, + ], + "timeTenseOptions": Array [ + Object { + "text": "Last", + "value": "last", + }, + Object { + "text": "Next", + "value": "next", + }, + ], + "timeUnits": Object { + "M": "month", + "d": "day", + "h": "hour", + "m": "minute", + "s": "second", + "w": "week", + "y": "year", + }, + "timeUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + Object { + "text": "days", + "value": "d", + }, + Object { + "text": "weeks", + "value": "w", + }, + Object { + "text": "months", + "value": "M", + }, + Object { + "text": "years", + "value": "y", + }, + ], + "timeUnitsPlural": Object { + "M": "months", + "d": "days", + "h": "hours", + "m": "minutes", + "s": "seconds", + "w": "weeks", + "y": "years", + }, + } + } /> } > @@ -300,7 +942,55 @@ exports[`EuiSuperDatePicker props compressed is rendered 1`] = ` disabled={false} onClick={[Function]} > - Last 15 minutes + @@ -430,6 +1120,188 @@ exports[`EuiSuperDatePicker props isQuickSelectOnly is rendered 1`] = ` recentlyUsedRanges={Array []} refreshInterval={1000} start="now-15m" + timeOptions={ + Object { + "commonDurationRanges": Array [ + Object { + "end": "now/d", + "label": "Today", + "start": "now/d", + }, + Object { + "end": "now/w", + "label": "This week", + "start": "now/w", + }, + Object { + "end": "now/M", + "label": "This month", + "start": "now/M", + }, + Object { + "end": "now/y", + "label": "This year", + "start": "now/y", + }, + Object { + "end": "now-1d/d", + "label": "Yesterday", + "start": "now-1d/d", + }, + Object { + "end": "now", + "label": "Week to date", + "start": "now/w", + }, + Object { + "end": "now", + "label": "Month to date", + "start": "now/M", + }, + Object { + "end": "now", + "label": "Year to date", + "start": "now/y", + }, + ], + "refreshUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + ], + "refreshUnitsShorthand": Object { + "d": "d", + "h": "h", + "m": "m", + "s": "s", + }, + "relativeOptions": Array [ + Object { + "text": "Seconds ago", + "value": "s", + }, + Object { + "text": "Minutes ago", + "value": "m", + }, + Object { + "text": "Hours ago", + "value": "h", + }, + Object { + "text": "Days ago", + "value": "d", + }, + Object { + "text": "Weeks ago", + "value": "w", + }, + Object { + "text": "Months ago", + "value": "M", + }, + Object { + "text": "Years ago", + "value": "y", + }, + Object { + "text": "Seconds from now", + "value": "s+", + }, + Object { + "text": "Minutes from now", + "value": "m+", + }, + Object { + "text": "Hours from now", + "value": "h+", + }, + Object { + "text": "Days from now", + "value": "d+", + }, + Object { + "text": "Weeks from now", + "value": "w+", + }, + Object { + "text": "Months from now", + "value": "M+", + }, + Object { + "text": "Years from now", + "value": "y+", + }, + ], + "timeTenseOptions": Array [ + Object { + "text": "Last", + "value": "last", + }, + Object { + "text": "Next", + "value": "next", + }, + ], + "timeUnits": Object { + "M": "month", + "d": "day", + "h": "hour", + "m": "minute", + "s": "second", + "w": "week", + "y": "year", + }, + "timeUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + Object { + "text": "days", + "value": "d", + }, + Object { + "text": "weeks", + "value": "w", + }, + Object { + "text": "months", + "value": "M", + }, + Object { + "text": "years", + "value": "y", + }, + ], + "timeUnitsPlural": Object { + "M": "months", + "d": "days", + "h": "hours", + "m": "minutes", + "s": "seconds", + "w": "weeks", + "y": "years", + }, + } + } /> } /> @@ -522,6 +1394,188 @@ exports[`EuiSuperDatePicker props showUpdateButton can be false 1`] = ` recentlyUsedRanges={Array []} refreshInterval={1000} start="now-15m" + timeOptions={ + Object { + "commonDurationRanges": Array [ + Object { + "end": "now/d", + "label": "Today", + "start": "now/d", + }, + Object { + "end": "now/w", + "label": "This week", + "start": "now/w", + }, + Object { + "end": "now/M", + "label": "This month", + "start": "now/M", + }, + Object { + "end": "now/y", + "label": "This year", + "start": "now/y", + }, + Object { + "end": "now-1d/d", + "label": "Yesterday", + "start": "now-1d/d", + }, + Object { + "end": "now", + "label": "Week to date", + "start": "now/w", + }, + Object { + "end": "now", + "label": "Month to date", + "start": "now/M", + }, + Object { + "end": "now", + "label": "Year to date", + "start": "now/y", + }, + ], + "refreshUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + ], + "refreshUnitsShorthand": Object { + "d": "d", + "h": "h", + "m": "m", + "s": "s", + }, + "relativeOptions": Array [ + Object { + "text": "Seconds ago", + "value": "s", + }, + Object { + "text": "Minutes ago", + "value": "m", + }, + Object { + "text": "Hours ago", + "value": "h", + }, + Object { + "text": "Days ago", + "value": "d", + }, + Object { + "text": "Weeks ago", + "value": "w", + }, + Object { + "text": "Months ago", + "value": "M", + }, + Object { + "text": "Years ago", + "value": "y", + }, + Object { + "text": "Seconds from now", + "value": "s+", + }, + Object { + "text": "Minutes from now", + "value": "m+", + }, + Object { + "text": "Hours from now", + "value": "h+", + }, + Object { + "text": "Days from now", + "value": "d+", + }, + Object { + "text": "Weeks from now", + "value": "w+", + }, + Object { + "text": "Months from now", + "value": "M+", + }, + Object { + "text": "Years from now", + "value": "y+", + }, + ], + "timeTenseOptions": Array [ + Object { + "text": "Last", + "value": "last", + }, + Object { + "text": "Next", + "value": "next", + }, + ], + "timeUnits": Object { + "M": "month", + "d": "day", + "h": "hour", + "m": "minute", + "s": "second", + "w": "week", + "y": "year", + }, + "timeUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + Object { + "text": "days", + "value": "d", + }, + Object { + "text": "weeks", + "value": "w", + }, + Object { + "text": "months", + "value": "M", + }, + Object { + "text": "years", + "value": "y", + }, + ], + "timeUnitsPlural": Object { + "M": "months", + "d": "days", + "h": "hours", + "m": "minutes", + "s": "seconds", + "w": "weeks", + "y": "years", + }, + } + } /> } > @@ -538,7 +1592,55 @@ exports[`EuiSuperDatePicker props showUpdateButton can be false 1`] = ` disabled={false} onClick={[Function]} > - Last 15 minutes + @@ -610,6 +1712,188 @@ exports[`EuiSuperDatePicker props showUpdateButton can be iconOnly 1`] = ` recentlyUsedRanges={Array []} refreshInterval={1000} start="now-15m" + timeOptions={ + Object { + "commonDurationRanges": Array [ + Object { + "end": "now/d", + "label": "Today", + "start": "now/d", + }, + Object { + "end": "now/w", + "label": "This week", + "start": "now/w", + }, + Object { + "end": "now/M", + "label": "This month", + "start": "now/M", + }, + Object { + "end": "now/y", + "label": "This year", + "start": "now/y", + }, + Object { + "end": "now-1d/d", + "label": "Yesterday", + "start": "now-1d/d", + }, + Object { + "end": "now", + "label": "Week to date", + "start": "now/w", + }, + Object { + "end": "now", + "label": "Month to date", + "start": "now/M", + }, + Object { + "end": "now", + "label": "Year to date", + "start": "now/y", + }, + ], + "refreshUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + ], + "refreshUnitsShorthand": Object { + "d": "d", + "h": "h", + "m": "m", + "s": "s", + }, + "relativeOptions": Array [ + Object { + "text": "Seconds ago", + "value": "s", + }, + Object { + "text": "Minutes ago", + "value": "m", + }, + Object { + "text": "Hours ago", + "value": "h", + }, + Object { + "text": "Days ago", + "value": "d", + }, + Object { + "text": "Weeks ago", + "value": "w", + }, + Object { + "text": "Months ago", + "value": "M", + }, + Object { + "text": "Years ago", + "value": "y", + }, + Object { + "text": "Seconds from now", + "value": "s+", + }, + Object { + "text": "Minutes from now", + "value": "m+", + }, + Object { + "text": "Hours from now", + "value": "h+", + }, + Object { + "text": "Days from now", + "value": "d+", + }, + Object { + "text": "Weeks from now", + "value": "w+", + }, + Object { + "text": "Months from now", + "value": "M+", + }, + Object { + "text": "Years from now", + "value": "y+", + }, + ], + "timeTenseOptions": Array [ + Object { + "text": "Last", + "value": "last", + }, + Object { + "text": "Next", + "value": "next", + }, + ], + "timeUnits": Object { + "M": "month", + "d": "day", + "h": "hour", + "m": "minute", + "s": "second", + "w": "week", + "y": "year", + }, + "timeUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + Object { + "text": "days", + "value": "d", + }, + Object { + "text": "weeks", + "value": "w", + }, + Object { + "text": "months", + "value": "M", + }, + Object { + "text": "years", + "value": "y", + }, + ], + "timeUnitsPlural": Object { + "M": "months", + "d": "days", + "h": "hours", + "m": "minutes", + "s": "seconds", + "w": "weeks", + "y": "years", + }, + } + } /> } > @@ -626,7 +1910,55 @@ exports[`EuiSuperDatePicker props showUpdateButton can be iconOnly 1`] = ` disabled={false} onClick={[Function]} > - Last 15 minutes + @@ -719,6 +2051,188 @@ exports[`EuiSuperDatePicker props width can be auto 1`] = ` recentlyUsedRanges={Array []} refreshInterval={1000} start="now-15m" + timeOptions={ + Object { + "commonDurationRanges": Array [ + Object { + "end": "now/d", + "label": "Today", + "start": "now/d", + }, + Object { + "end": "now/w", + "label": "This week", + "start": "now/w", + }, + Object { + "end": "now/M", + "label": "This month", + "start": "now/M", + }, + Object { + "end": "now/y", + "label": "This year", + "start": "now/y", + }, + Object { + "end": "now-1d/d", + "label": "Yesterday", + "start": "now-1d/d", + }, + Object { + "end": "now", + "label": "Week to date", + "start": "now/w", + }, + Object { + "end": "now", + "label": "Month to date", + "start": "now/M", + }, + Object { + "end": "now", + "label": "Year to date", + "start": "now/y", + }, + ], + "refreshUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + ], + "refreshUnitsShorthand": Object { + "d": "d", + "h": "h", + "m": "m", + "s": "s", + }, + "relativeOptions": Array [ + Object { + "text": "Seconds ago", + "value": "s", + }, + Object { + "text": "Minutes ago", + "value": "m", + }, + Object { + "text": "Hours ago", + "value": "h", + }, + Object { + "text": "Days ago", + "value": "d", + }, + Object { + "text": "Weeks ago", + "value": "w", + }, + Object { + "text": "Months ago", + "value": "M", + }, + Object { + "text": "Years ago", + "value": "y", + }, + Object { + "text": "Seconds from now", + "value": "s+", + }, + Object { + "text": "Minutes from now", + "value": "m+", + }, + Object { + "text": "Hours from now", + "value": "h+", + }, + Object { + "text": "Days from now", + "value": "d+", + }, + Object { + "text": "Weeks from now", + "value": "w+", + }, + Object { + "text": "Months from now", + "value": "M+", + }, + Object { + "text": "Years from now", + "value": "y+", + }, + ], + "timeTenseOptions": Array [ + Object { + "text": "Last", + "value": "last", + }, + Object { + "text": "Next", + "value": "next", + }, + ], + "timeUnits": Object { + "M": "month", + "d": "day", + "h": "hour", + "m": "minute", + "s": "second", + "w": "week", + "y": "year", + }, + "timeUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + Object { + "text": "days", + "value": "d", + }, + Object { + "text": "weeks", + "value": "w", + }, + Object { + "text": "months", + "value": "M", + }, + Object { + "text": "years", + "value": "y", + }, + ], + "timeUnitsPlural": Object { + "M": "months", + "d": "days", + "h": "hours", + "m": "minutes", + "s": "seconds", + "w": "weeks", + "y": "years", + }, + } + } /> } > @@ -735,7 +2249,55 @@ exports[`EuiSuperDatePicker props width can be auto 1`] = ` disabled={false} onClick={[Function]} > - Last 15 minutes + @@ -828,6 +2390,188 @@ exports[`EuiSuperDatePicker props width can be full 1`] = ` recentlyUsedRanges={Array []} refreshInterval={1000} start="now-15m" + timeOptions={ + Object { + "commonDurationRanges": Array [ + Object { + "end": "now/d", + "label": "Today", + "start": "now/d", + }, + Object { + "end": "now/w", + "label": "This week", + "start": "now/w", + }, + Object { + "end": "now/M", + "label": "This month", + "start": "now/M", + }, + Object { + "end": "now/y", + "label": "This year", + "start": "now/y", + }, + Object { + "end": "now-1d/d", + "label": "Yesterday", + "start": "now-1d/d", + }, + Object { + "end": "now", + "label": "Week to date", + "start": "now/w", + }, + Object { + "end": "now", + "label": "Month to date", + "start": "now/M", + }, + Object { + "end": "now", + "label": "Year to date", + "start": "now/y", + }, + ], + "refreshUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + ], + "refreshUnitsShorthand": Object { + "d": "d", + "h": "h", + "m": "m", + "s": "s", + }, + "relativeOptions": Array [ + Object { + "text": "Seconds ago", + "value": "s", + }, + Object { + "text": "Minutes ago", + "value": "m", + }, + Object { + "text": "Hours ago", + "value": "h", + }, + Object { + "text": "Days ago", + "value": "d", + }, + Object { + "text": "Weeks ago", + "value": "w", + }, + Object { + "text": "Months ago", + "value": "M", + }, + Object { + "text": "Years ago", + "value": "y", + }, + Object { + "text": "Seconds from now", + "value": "s+", + }, + Object { + "text": "Minutes from now", + "value": "m+", + }, + Object { + "text": "Hours from now", + "value": "h+", + }, + Object { + "text": "Days from now", + "value": "d+", + }, + Object { + "text": "Weeks from now", + "value": "w+", + }, + Object { + "text": "Months from now", + "value": "M+", + }, + Object { + "text": "Years from now", + "value": "y+", + }, + ], + "timeTenseOptions": Array [ + Object { + "text": "Last", + "value": "last", + }, + Object { + "text": "Next", + "value": "next", + }, + ], + "timeUnits": Object { + "M": "month", + "d": "day", + "h": "hour", + "m": "minute", + "s": "second", + "w": "week", + "y": "year", + }, + "timeUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + Object { + "text": "days", + "value": "d", + }, + Object { + "text": "weeks", + "value": "w", + }, + Object { + "text": "months", + "value": "M", + }, + Object { + "text": "years", + "value": "y", + }, + ], + "timeUnitsPlural": Object { + "M": "months", + "d": "days", + "h": "hours", + "m": "minutes", + "s": "seconds", + "w": "weeks", + "y": "years", + }, + } + } /> } > @@ -844,7 +2588,55 @@ exports[`EuiSuperDatePicker props width can be full 1`] = ` disabled={false} onClick={[Function]} > - Last 15 minutes + diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap b/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap index 15edcb536b6f..35fedb8d88fd 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap +++ b/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap @@ -40,6 +40,188 @@ exports[`EuiQuickSelectPopover is rendered 1`] = ` applyTime={[Function]} end="now" start="now-15m" + timeOptions={ + Object { + "commonDurationRanges": Array [ + Object { + "end": "now/d", + "label": "Today", + "start": "now/d", + }, + Object { + "end": "now/w", + "label": "This week", + "start": "now/w", + }, + Object { + "end": "now/M", + "label": "This month", + "start": "now/M", + }, + Object { + "end": "now/y", + "label": "This year", + "start": "now/y", + }, + Object { + "end": "now-1d/d", + "label": "Yesterday", + "start": "now-1d/d", + }, + Object { + "end": "now", + "label": "Week to date", + "start": "now/w", + }, + Object { + "end": "now", + "label": "Month to date", + "start": "now/M", + }, + Object { + "end": "now", + "label": "Year to date", + "start": "now/y", + }, + ], + "refreshUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + ], + "refreshUnitsShorthand": Object { + "d": "d", + "h": "h", + "m": "m", + "s": "s", + }, + "relativeOptions": Array [ + Object { + "text": "Seconds ago", + "value": "s", + }, + Object { + "text": "Minutes ago", + "value": "m", + }, + Object { + "text": "Hours ago", + "value": "h", + }, + Object { + "text": "Days ago", + "value": "d", + }, + Object { + "text": "Weeks ago", + "value": "w", + }, + Object { + "text": "Months ago", + "value": "M", + }, + Object { + "text": "Years ago", + "value": "y", + }, + Object { + "text": "Seconds from now", + "value": "s+", + }, + Object { + "text": "Minutes from now", + "value": "m+", + }, + Object { + "text": "Hours from now", + "value": "h+", + }, + Object { + "text": "Days from now", + "value": "d+", + }, + Object { + "text": "Weeks from now", + "value": "w+", + }, + Object { + "text": "Months from now", + "value": "M+", + }, + Object { + "text": "Years from now", + "value": "y+", + }, + ], + "timeTenseOptions": Array [ + Object { + "text": "Last", + "value": "last", + }, + Object { + "text": "Next", + "value": "next", + }, + ], + "timeUnits": Object { + "M": "month", + "d": "day", + "h": "hour", + "m": "minute", + "s": "second", + "w": "week", + "y": "year", + }, + "timeUnitsOptions": Array [ + Object { + "text": "seconds", + "value": "s", + }, + Object { + "text": "minutes", + "value": "m", + }, + Object { + "text": "hours", + "value": "h", + }, + Object { + "text": "days", + "value": "d", + }, + Object { + "text": "weeks", + "value": "w", + }, + Object { + "text": "months", + "value": "M", + }, + Object { + "text": "years", + "value": "y", + }, + ], + "timeUnitsPlural": Object { + "M": "months", + "d": "days", + "h": "hours", + "m": "minutes", + "s": "seconds", + "w": "weeks", + "y": "years", + }, + } + } /> {}; const defaultProps = { @@ -20,22 +21,33 @@ const defaultProps = { describe('EuiQuickSelect', () => { test('is rendered', () => { - const component = shallow(); + const component = shallow( + + {(timeOptions) => ( + + )} + + ).dive(); expect(component).toMatchSnapshot(); }); test('prevQuickSelect', () => { const component = shallow( - - ); + + {(timeOptions) => ( + + )} + + ).dive(); expect(component).toMatchSnapshot(); }); diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.test.tsx b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.test.tsx index 1564c54fffc3..7389468739c8 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.test.tsx +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { RenderI18nTimeOptions } from '../time_options'; import { EuiQuickSelectPopover, EuiQuickSelectPopoverProps, @@ -16,7 +17,7 @@ import { const noop = () => {}; -const defaultProps: EuiQuickSelectPopoverProps = { +const defaultProps: Omit = { applyTime: noop, applyRefreshInterval: noop, start: 'now-15m', @@ -31,8 +32,13 @@ const defaultProps: EuiQuickSelectPopoverProps = { describe('EuiQuickSelectPopover', () => { test('is rendered', () => { - const component = shallow(); - + const component = shallow( + + {(timeOptions) => ( + + )} + + ).dive(); expect(component).toMatchSnapshot(); }); }); From f28d90245be1d519458aa41b34ddf1f5ab3d6ac7 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 23 Mar 2022 12:21:40 -0700 Subject: [PATCH 16/29] [REVERT] Test moment locale --- src-docs/src/views/super_date_picker/super_date_picker.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src-docs/src/views/super_date_picker/super_date_picker.tsx b/src-docs/src/views/super_date_picker/super_date_picker.tsx index c2575fe0da07..6774d7dd889d 100644 --- a/src-docs/src/views/super_date_picker/super_date_picker.tsx +++ b/src-docs/src/views/super_date_picker/super_date_picker.tsx @@ -90,6 +90,7 @@ export default () => { end={end} onTimeChange={onTimeChange} onRefresh={onRefresh} + locale="ja" /> ); From d4fb25012d443c51869427ddb03974890c2045e9 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 23 Mar 2022 12:43:23 -0700 Subject: [PATCH 17/29] Add changelog entry --- upcoming_changelogs/5743.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 upcoming_changelogs/5743.md diff --git a/upcoming_changelogs/5743.md b/upcoming_changelogs/5743.md new file mode 100644 index 000000000000..6d5bedfa66d1 --- /dev/null +++ b/upcoming_changelogs/5743.md @@ -0,0 +1,6 @@ +- Added all remaining missing i18n tokens for `EuiSuperDatePicker` + +**Breaking changes** + +- Removed `prettyDuration` utility exported by `EuiSuperDatePicker` - this util was converted to a `PrettyDuration` component and `usePrettyDuration` hook which should be used instead +- Removed `commonDurationRanges` exported by `EuiSuperDatePicker`. The new pretty duration utils will fall back to `commonDurationRanges` by default if no `quickRanges` are passed From 2bfd68264403ad916412d247ef6fde7e07fc9c14 Mon Sep 17 00:00:00 2001 From: Constance Date: Wed, 23 Mar 2022 13:17:20 -0700 Subject: [PATCH 18/29] derpin --- src/components/date_picker/super_date_picker/time_options.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/date_picker/super_date_picker/time_options.tsx b/src/components/date_picker/super_date_picker/time_options.tsx index 2f154ff5873d..7b2826d885df 100644 --- a/src/components/date_picker/super_date_picker/time_options.tsx +++ b/src/components/date_picker/super_date_picker/time_options.tsx @@ -134,8 +134,8 @@ export const useI18nTimeOptions = () => { const refreshUnitsShorthand = { s: useEuiI18n('euiTimeOptions.secondsShorthand', 's'), - m: useEuiI18n('euiTimeOptions.monthsShorthand', 'm'), - h: useEuiI18n('euiTimeOptions.monthsShorthand', 'h'), + m: useEuiI18n('euiTimeOptions.minutesShorthand', 'm'), + h: useEuiI18n('euiTimeOptions.hoursShorthand', 'h'), d: useEuiI18n('euiTimeOptions.daysShorthand', 'd'), }; From b837a56efc5ca5da1467ac8d66381904d02b08a0 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 23 Mar 2022 13:55:26 -0700 Subject: [PATCH 19/29] Update Pretty duration docs --- .../views/pretty_duration/pretty_duration.js | 39 ++++++++-- .../pretty_duration_example.js | 71 +++++++++++-------- 2 files changed, 74 insertions(+), 36 deletions(-) diff --git a/src-docs/src/views/pretty_duration/pretty_duration.js b/src-docs/src/views/pretty_duration/pretty_duration.js index 735b2c49999b..1c61120313cf 100644 --- a/src-docs/src/views/pretty_duration/pretty_duration.js +++ b/src-docs/src/views/pretty_duration/pretty_duration.js @@ -4,7 +4,7 @@ import { EuiSpacer, EuiCodeBlock, EuiText, - prettyDuration, + usePrettyDuration, } from '../../../../src/components'; const examples = [ @@ -50,6 +50,12 @@ const examples = [ ], dateFormat: 'MMMM Do YYYY, HH:mm:ss.SSS', }, + { + // Example that will use a default common quick range label + start: 'now/w', + end: 'now', + dateFormat: 'MMMM Do YYYY, HH:mm:ss.SSS', + }, ]; export default function prettyDurationExample() { @@ -57,16 +63,35 @@ export default function prettyDurationExample() { {examples.map(({ start, end, quickRanges, dateFormat }, idx) => (
- - prettyDuration('{start}', '{end}',{' '} - {JSON.stringify(quickRanges)}, ' - {dateFormat}') - + {` + +usePrettyDuration({ + timeFrom: '${start}', + timeTo: '${end}', + dateFormat: '${dateFormat}', + quickRanges: ${JSON.stringify(quickRanges)}, +})`} -

{prettyDuration(start, end, quickRanges, dateFormat)}

+

+ {usePrettyDuration({ + timeFrom: start, + timeTo: end, + quickRanges, + dateFormat, + })} +

diff --git a/src-docs/src/views/pretty_duration/pretty_duration_example.js b/src-docs/src/views/pretty_duration/pretty_duration_example.js index d97a32b4a912..e4604553c3b9 100644 --- a/src-docs/src/views/pretty_duration/pretty_duration_example.js +++ b/src-docs/src/views/pretty_duration/pretty_duration_example.js @@ -6,10 +6,10 @@ import { EuiAccordion, EuiCode, EuiCodeBlock, - EuiSpacer, - commonDurationRanges, } from '../../../../src/components'; +import { RenderI18nTimeOptions } from '../../../../src/components/date_picker/super_date_picker/time_options'; + import PrettyDuration from './pretty_duration'; const prettyDurationSource = require('!!raw-loader!./pretty_duration'); @@ -26,37 +26,50 @@ export const PrettyDurationExample = { text: (

- Use prettyDuration to convert a start and end - date string to a human-friendly format. + Use the {''} component (for JSX + display) or usePrettyDuration() hook (for + attribute strings) to convert a start and end date string to a + human-friendly format. Both utilities take the following props:

-

- Start and end values for the duration are passed as the first and - second arguments, respectively. These can be timestamps ( - 2018-01-17T18:57:57.149Z) or relative times ( - now-15m). -

+
    +
  • +

    + timeFrom and timeTo accept + start and end date values for the duration. These can be + timestamps (2018-01-17T18:57:57.149Z) or + relative times (now-15m). +

    +
  • -

    - An array of quick range values is passed as the third argument. - These are used to pretty format custom ranges. EUI exports - commonDurationRanges which can be passed here. -

    +
  • +

    + dateFormat specifies the output date/time + format. +

    +
  • - - - {JSON.stringify(commonDurationRanges, null, 2)} - - - - - -

    - The output date/time format is specified by the fourth argument. -

    +
  • +

    + quickRanges optionally accepts an array of + quick range values that are used to pretty format custom ranges. + If no custom quick ranges are passed, EUI will default to a set + of common duration ranges defined below. +

    + + + + {({ commonDurationRanges }) => + JSON.stringify(commonDurationRanges, null, 2) + } + + + +
  • +
), demo: , From 83bc9a3f3f927750ce03bb04c913dc4f46adb447 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 23 Mar 2022 14:35:37 -0700 Subject: [PATCH 20/29] [PR feedback] FC typing --- .../date_picker/super_date_picker/super_date_picker.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/date_picker/super_date_picker/super_date_picker.tsx b/src/components/date_picker/super_date_picker/super_date_picker.tsx index f07fd91132e5..fdc8751acba3 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.tsx +++ b/src/components/date_picker/super_date_picker/super_date_picker.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { Component } from 'react'; +import React, { Component, FunctionComponent } from 'react'; import classNames from 'classnames'; import moment, { LocaleSpecifier } from 'moment'; // eslint-disable-line import/named import dateMath from '@elastic/datemath'; @@ -626,7 +626,9 @@ export class EuiSuperDatePickerInternal extends Component< // we have to use a render prop here in order for us to pass i18n'd strings/objects/etc // to all underlying usages of our timeOptions constants. If someday we convert // EuiSuperDatePicker to an FC, we can likely get rid of this wrapper. -export const EuiSuperDatePicker = (props: EuiSuperDatePickerProps) => ( +export const EuiSuperDatePicker: FunctionComponent = ( + props +) => ( {(timeOptions) => ( Date: Thu, 24 Mar 2022 14:42:29 -0700 Subject: [PATCH 21/29] [i18n feedback] Remove `timeUnits` and `timeUnitsPlural` Per i18n team feedback, we shouldn't be chopping units up like this and concatenating them manually - we should prefer to attempt to tokenize whole sentences to better accommodate the different grammars of different languages + add more misc comment documentation + capitalize units in timeUnitOptions to match relativeOptions --- .../super_date_picker/time_options.test.tsx | 42 +++------- .../super_date_picker/time_options.tsx | 83 ++++++++----------- 2 files changed, 46 insertions(+), 79 deletions(-) diff --git a/src/components/date_picker/super_date_picker/time_options.test.tsx b/src/components/date_picker/super_date_picker/time_options.test.tsx index a57abc6dc076..60d93be88217 100644 --- a/src/components/date_picker/super_date_picker/time_options.test.tsx +++ b/src/components/date_picker/super_date_picker/time_options.test.tsx @@ -62,15 +62,15 @@ describe('useI18nTimeOptions', () => { ], "refreshUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, ], @@ -148,54 +148,36 @@ describe('useI18nTimeOptions', () => { "value": "next", }, ], - "timeUnits": Object { - "M": "month", - "d": "day", - "h": "hour", - "m": "minute", - "s": "second", - "w": "week", - "y": "year", - }, "timeUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, Object { - "text": "days", + "text": "Days", "value": "d", }, Object { - "text": "weeks", + "text": "Weeks", "value": "w", }, Object { - "text": "months", + "text": "Months", "value": "M", }, Object { - "text": "years", + "text": "Years", "value": "y", }, ], - "timeUnitsPlural": Object { - "M": "months", - "d": "days", - "h": "hours", - "m": "minutes", - "s": "seconds", - "w": "weeks", - "y": "years", - }, } `); }); @@ -205,13 +187,13 @@ describe('RenderI18nTimeOptions', () => { it('is a render function that passes the underlying children timeOptions as an arg', () => { const component = shallow( - {({ timeUnits }) => <>{timeUnits.m}} + {({ timeUnitsOptions }) => <>{timeUnitsOptions[0].text}} ); expect(component).toMatchInlineSnapshot(` - minute + Seconds `); }); diff --git a/src/components/date_picker/super_date_picker/time_options.tsx b/src/components/date_picker/super_date_picker/time_options.tsx index 7b2826d885df..7aa1b2d3db7a 100644 --- a/src/components/date_picker/super_date_picker/time_options.tsx +++ b/src/components/date_picker/super_date_picker/time_options.tsx @@ -6,67 +6,49 @@ * Side Public License, v 1. */ -import { keysOf } from '../../common'; import { useEuiI18n } from '../../i18n'; import { EuiSelectOption } from '../../form'; -import { - TimeUnitId, - TimeUnitLabel, - TimeUnitLabelPlural, - RelativeOption, - DurationRange, -} from '../types'; +import { TimeUnitId, RelativeOption, DurationRange } from '../types'; export const LAST = 'last'; export const NEXT = 'next'; export type TimeOptions = { timeTenseOptions: EuiSelectOption[]; - timeUnits: { [id in TimeUnitId]: TimeUnitLabel }; timeUnitsOptions: EuiSelectOption[]; - timeUnitsPlural: { [id in TimeUnitId]: TimeUnitLabelPlural }; relativeOptions: RelativeOption[]; - refreshUnitsOptions: RelativeOption[]; + refreshUnitsOptions: EuiSelectOption[]; refreshUnitsShorthand: { [id: string]: string }; commonDurationRanges: DurationRange[]; }; -export const useI18nTimeOptions = () => { +/** + * i18n'd time options, mostly used in EuiSelects (except for a few cases) + * used in EuiSuperDatePicker child sub-components + */ +export const useI18nTimeOptions = (): TimeOptions => { + /** + * Quick select panel + */ const timeTenseOptions = [ - { - value: LAST, - text: useEuiI18n('euiTimeOptions.last', 'Last'), - }, - { - value: NEXT, - text: useEuiI18n('euiTimeOptions.next', 'Next'), - }, + { value: LAST, text: useEuiI18n('euiTimeOptions.last', 'Last') }, + { value: NEXT, text: useEuiI18n('euiTimeOptions.next', 'Next') }, ]; - const timeUnits = { - s: useEuiI18n('euiTimeOptions.second', 'second'), - m: useEuiI18n('euiTimeOptions.minute', 'minute'), - h: useEuiI18n('euiTimeOptions.hour', 'hour'), - d: useEuiI18n('euiTimeOptions.day', 'day'), - w: useEuiI18n('euiTimeOptions.week', 'week'), - M: useEuiI18n('euiTimeOptions.month', 'month'), - y: useEuiI18n('euiTimeOptions.year', 'year'), - }; - const timeUnitsOptions = keysOf(timeUnits).map((key) => { - return { value: key, text: `${timeUnits[key]}s` }; - }); - - const timeUnitsPlural = { - s: useEuiI18n('euiTimeOptions.seconds', 'seconds'), - m: useEuiI18n('euiTimeOptions.minutes', 'minutes'), - h: useEuiI18n('euiTimeOptions.hours', 'hours'), - d: useEuiI18n('euiTimeOptions.days', 'days'), - w: useEuiI18n('euiTimeOptions.weeks', 'weeks'), - M: useEuiI18n('euiTimeOptions.months', 'months'), - y: useEuiI18n('euiTimeOptions.years', 'years'), - }; + const timeUnitsOptions = [ + { value: 's', text: useEuiI18n('euiTimeOptions.seconds', 'Seconds') }, + { value: 'm', text: useEuiI18n('euiTimeOptions.minutes', 'Minutes') }, + { value: 'h', text: useEuiI18n('euiTimeOptions.hours', 'Hours') }, + { value: 'd', text: useEuiI18n('euiTimeOptions.days', 'Days') }, + { value: 'w', text: useEuiI18n('euiTimeOptions.weeks', 'Weeks') }, + { value: 'M', text: useEuiI18n('euiTimeOptions.months', 'Months') }, + { value: 'y', text: useEuiI18n('euiTimeOptions.years', 'Years') }, + ]; + /** + * Relative tab + */ const relativeOptions: RelativeOption[] = [ { text: useEuiI18n('euiTimeOptions.secondsAgo', 'Seconds ago'), @@ -126,11 +108,12 @@ export const useI18nTimeOptions = () => { }, ]; - const refreshUnitsOptions = keysOf(timeUnits) - .filter( - (timeUnit) => timeUnit === 'h' || timeUnit === 'm' || timeUnit === 's' - ) - .map((timeUnit) => ({ value: timeUnit, text: timeUnitsPlural[timeUnit] })); + /** + * Auto Refresh + */ + const refreshUnitsOptions = timeUnitsOptions.filter( + ({ value }) => value === 'h' || value === 'm' || value === 's' + ); const refreshUnitsShorthand = { s: useEuiI18n('euiTimeOptions.secondsShorthand', 's'), @@ -139,6 +122,9 @@ export const useI18nTimeOptions = () => { d: useEuiI18n('euiTimeOptions.daysShorthand', 'd'), }; + /** + * Used by both Quick Select ('Commonly used') and by PrettyDuration + */ const commonDurationRanges = [ { start: 'now/d', @@ -184,9 +170,7 @@ export const useI18nTimeOptions = () => { return { timeTenseOptions, - timeUnits, timeUnitsOptions, - timeUnitsPlural, relativeOptions, refreshUnitsOptions, refreshUnitsShorthand, @@ -194,6 +178,7 @@ export const useI18nTimeOptions = () => { }; }; +// Render function of the above, used by class components that can't use hooks export const RenderI18nTimeOptions = (props: { children: (args: TimeOptions) => any; }) => { From e933241c2ba618f1ec6269292ddc602eda226a03 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 24 Mar 2022 14:47:55 -0700 Subject: [PATCH 22/29] [i18n feedback] Remove concatenated unit string in relative tab - preferring to let languages translate each phrase per unit --- .../date_popover/relative_tab.tsx | 22 ++++++------------- .../super_date_picker/time_options.test.tsx | 9 ++++++++ .../super_date_picker/time_options.tsx | 12 ++++++++++ 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx b/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx index a8ca449b63c5..08c19ee00854 100644 --- a/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx @@ -105,7 +105,7 @@ export class EuiRelativeTab extends Component< }; render() { - const { timeUnits, relativeOptions } = this.props.timeOptions; + const { relativeOptions, relativeRoundingLabels } = this.props.timeOptions; const { count, unit } = this.state; const invalidDate = this.props.value === INVALID_DATE; const invalidValue = count === undefined || count < 0; @@ -216,20 +216,12 @@ export class EuiRelativeTab extends Component< - - {(roundingLabel: string) => ( - - )} - + ); diff --git a/src/components/date_picker/super_date_picker/time_options.test.tsx b/src/components/date_picker/super_date_picker/time_options.test.tsx index 60d93be88217..bd2c24bc4da5 100644 --- a/src/components/date_picker/super_date_picker/time_options.test.tsx +++ b/src/components/date_picker/super_date_picker/time_options.test.tsx @@ -138,6 +138,15 @@ describe('useI18nTimeOptions', () => { "value": "y+", }, ], + "relativeRoundingLabels": Object { + "M": "Round to the month", + "d": "Round to the day", + "h": "Round to the hour", + "m": "Round to the minute", + "s": "Round to the second", + "w": "Round to the week", + "y": "Round to the year", + }, "timeTenseOptions": Array [ Object { "text": "Last", diff --git a/src/components/date_picker/super_date_picker/time_options.tsx b/src/components/date_picker/super_date_picker/time_options.tsx index 7aa1b2d3db7a..30968f610f54 100644 --- a/src/components/date_picker/super_date_picker/time_options.tsx +++ b/src/components/date_picker/super_date_picker/time_options.tsx @@ -18,6 +18,7 @@ export type TimeOptions = { timeTenseOptions: EuiSelectOption[]; timeUnitsOptions: EuiSelectOption[]; relativeOptions: RelativeOption[]; + relativeRoundingLabels: { [id in TimeUnitId]: string }; refreshUnitsOptions: EuiSelectOption[]; refreshUnitsShorthand: { [id: string]: string }; commonDurationRanges: DurationRange[]; @@ -108,6 +109,16 @@ export const useI18nTimeOptions = (): TimeOptions => { }, ]; + const relativeRoundingLabels = { + s: useEuiI18n('euiTimeOptions.roundToSecond', 'Round to the second'), + m: useEuiI18n('euiTimeOptions.roundToMinute', 'Round to the minute'), + h: useEuiI18n('euiTimeOptions.roundToHour', 'Round to the hour'), + d: useEuiI18n('euiTimeOptions.roundToDay', 'Round to the day'), + w: useEuiI18n('euiTimeOptions.roundToWeek', 'Round to the week'), + M: useEuiI18n('euiTimeOptions.roundToMonth', 'Round to the month'), + y: useEuiI18n('euiTimeOptions.roundToYear', 'Round to the year'), + }; + /** * Auto Refresh */ @@ -172,6 +183,7 @@ export const useI18nTimeOptions = (): TimeOptions => { timeTenseOptions, timeUnitsOptions, relativeOptions, + relativeRoundingLabels, refreshUnitsOptions, refreshUnitsShorthand, commonDurationRanges, From 28c937f55dba345c3686c455acf4920717161154 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 24 Mar 2022 14:53:13 -0700 Subject: [PATCH 23/29] [i18n feedback] Remove concatenated unit strings in pretty_interval - allows consuming applications to determine grammar/number placement, pluralization, etc --- .../super_date_picker/pretty_interval.ts | 73 ++++++++++++------- .../super_date_picker/time_options.test.tsx | 6 -- .../super_date_picker/time_options.tsx | 9 --- 3 files changed, 47 insertions(+), 41 deletions(-) diff --git a/src/components/date_picker/super_date_picker/pretty_interval.ts b/src/components/date_picker/super_date_picker/pretty_interval.ts index b5e28d6eeab0..cced154246e1 100644 --- a/src/components/date_picker/super_date_picker/pretty_interval.ts +++ b/src/components/date_picker/super_date_picker/pretty_interval.ts @@ -7,7 +7,6 @@ */ import { useEuiI18n } from '../../i18n'; -import { useI18nTimeOptions } from './time_options'; const MS_IN_SECOND = 1000; const MS_IN_MINUTE = 60 * MS_IN_SECOND; @@ -16,6 +15,49 @@ const MS_IN_DAY = 24 * MS_IN_HOUR; type IntervalUnitId = 's' | 'm' | 'h' | 'd'; +/** + * Pretty interval i18n strings + * + * Units should not be simply concatenated because different languages + * will have different grammar/positions for time than English + */ +const usePrettyIntervalI18n = (interval: number) => ({ + s: useEuiI18n( + 'euiPrettyInterval.seconds', + ({ interval }) => `${interval} second${interval > 1 ? 's' : ''}`, + { interval } + ), + m: useEuiI18n( + 'euiPrettyInterval.minutes', + ({ interval }) => `${interval} minute${interval > 1 ? 's' : ''}`, + { interval } + ), + h: useEuiI18n( + 'euiPrettyInterval.hours', + ({ interval }) => `${interval} hour${interval > 1 ? 's' : ''}`, + { interval } + ), + d: useEuiI18n( + 'euiPrettyInterval.days', + ({ interval }) => `${interval} day${interval > 1 ? 's' : ''}`, + { interval } + ), + shorthand: { + s: useEuiI18n('euiPrettyInterval.secondsShorthand', '{interval} s', { + interval, + }), + m: useEuiI18n('euiPrettyInterval.minutesShorthand', '{interval} m', { + interval, + }), + h: useEuiI18n('euiPrettyInterval.hoursShorthand', '{interval} h', { + interval, + }), + d: useEuiI18n('euiPrettyInterval.daysShorthand', '{interval} d', { + interval, + }), + }, +}); + export const usePrettyInterval = ( isPaused: boolean, intervalInMs: number, @@ -38,7 +80,10 @@ export const usePrettyInterval = ( interval = Math.round(intervalInMs / MS_IN_DAY); unitId = 'd'; } - prettyInterval = useI18nUnits(interval, unitId, shortHand); + const prettyIntervalI18n = usePrettyIntervalI18n(interval); + prettyInterval = shortHand + ? prettyIntervalI18n.shorthand[unitId] + : prettyIntervalI18n[unitId]; const off = useEuiI18n('euiPrettyInterval.off', 'Off'); if (isPaused || intervalInMs === 0) { @@ -47,27 +92,3 @@ export const usePrettyInterval = ( return prettyInterval; }; - -const useI18nUnits = ( - interval: number, - unitId: IntervalUnitId, - shortHand: boolean -) => { - const { - timeUnits, - timeUnitsPlural, - refreshUnitsShorthand, - } = useI18nTimeOptions(); - - let units = ''; - - if (shortHand) { - units = refreshUnitsShorthand[unitId]; - } else if (interval > 1) { - units = timeUnitsPlural[unitId]; - } else { - units = timeUnits[unitId]; - } - - return `${interval} ${units}`; -}; diff --git a/src/components/date_picker/super_date_picker/time_options.test.tsx b/src/components/date_picker/super_date_picker/time_options.test.tsx index bd2c24bc4da5..092ccb755e95 100644 --- a/src/components/date_picker/super_date_picker/time_options.test.tsx +++ b/src/components/date_picker/super_date_picker/time_options.test.tsx @@ -74,12 +74,6 @@ describe('useI18nTimeOptions', () => { "value": "h", }, ], - "refreshUnitsShorthand": Object { - "d": "d", - "h": "h", - "m": "m", - "s": "s", - }, "relativeOptions": Array [ Object { "text": "Seconds ago", diff --git a/src/components/date_picker/super_date_picker/time_options.tsx b/src/components/date_picker/super_date_picker/time_options.tsx index 30968f610f54..eda29e7b9576 100644 --- a/src/components/date_picker/super_date_picker/time_options.tsx +++ b/src/components/date_picker/super_date_picker/time_options.tsx @@ -20,7 +20,6 @@ export type TimeOptions = { relativeOptions: RelativeOption[]; relativeRoundingLabels: { [id in TimeUnitId]: string }; refreshUnitsOptions: EuiSelectOption[]; - refreshUnitsShorthand: { [id: string]: string }; commonDurationRanges: DurationRange[]; }; @@ -126,13 +125,6 @@ export const useI18nTimeOptions = (): TimeOptions => { ({ value }) => value === 'h' || value === 'm' || value === 's' ); - const refreshUnitsShorthand = { - s: useEuiI18n('euiTimeOptions.secondsShorthand', 's'), - m: useEuiI18n('euiTimeOptions.minutesShorthand', 'm'), - h: useEuiI18n('euiTimeOptions.hoursShorthand', 'h'), - d: useEuiI18n('euiTimeOptions.daysShorthand', 'd'), - }; - /** * Used by both Quick Select ('Commonly used') and by PrettyDuration */ @@ -185,7 +177,6 @@ export const useI18nTimeOptions = (): TimeOptions => { relativeOptions, relativeRoundingLabels, refreshUnitsOptions, - refreshUnitsShorthand, commonDurationRanges, }; }; From a277a663cea273e7b6ca45ee5fc38c8395ddfddb Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 24 Mar 2022 15:03:12 -0700 Subject: [PATCH 24/29] [i18n feedback] Remove concatenated strings in pretty_duration - allows consuming applications to determine grammar/number placement, pluralization, etc - pretty_duration has its own custom i18n strings not shared with other parts of EuiDatePicker, so I put them at the top of the file instead of in time_options + refactored/greatly simplified relativeDuration logic - a lot of the necessary keys were already in relativeParts, no need to substr, get timeTense, etc - structure is still somewhat weird due to not being able to call hooks conditionally + DRY out TimeUnitAllId for string maps with both `+` and `-` time units + add comment docblocks for readability/organization --- .../super_date_picker/pretty_duration.tsx | 183 +++++++++++++++--- src/components/date_picker/types.ts | 3 +- 2 files changed, 156 insertions(+), 30 deletions(-) diff --git a/src/components/date_picker/super_date_picker/pretty_duration.tsx b/src/components/date_picker/super_date_picker/pretty_duration.tsx index 3d53a3e0dc78..e64d1b9f5680 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.tsx +++ b/src/components/date_picker/super_date_picker/pretty_duration.tsx @@ -13,7 +13,133 @@ import { useEuiI18n } from '../../i18n'; import { getDateMode, DATE_MODES } from './date_modes'; import { parseRelativeParts } from './relative_utils'; import { useI18nTimeOptions } from './time_options'; -import { DurationRange, TimeUnitId, ShortDate, RelativeParts } from '../types'; +import { + DurationRange, + TimeUnitAllId, + ShortDate, + RelativeParts, +} from '../types'; + +/** + * Pretty duration i18n strings + * Units should not be simply concatenated because different languages + * will have different grammar/positions for time than English + */ + +const useRelativeDurationI18n = (duration: number) => ({ + s: useEuiI18n( + 'euiPrettyDuration.lastDurationSeconds', + ({ duration }) => `Last ${duration} second${duration === 1 ? '' : 's'}`, + { duration } + ), + 's+': useEuiI18n( + 'euiPrettyDuration.nextDurationSeconds', + ({ duration }) => `Next ${duration} second${duration === 1 ? '' : 's'}`, + { duration } + ), + m: useEuiI18n( + 'euiPrettyDuration.lastDurationMinutes', + ({ duration }) => `Last ${duration} minute${duration === 1 ? '' : 's'}`, + { duration } + ), + 'm+': useEuiI18n( + 'euiPrettyDuration.nextDurationMinutes', + ({ duration }) => `Next ${duration} minute${duration === 1 ? '' : 's'}`, + { duration } + ), + h: useEuiI18n( + 'euiPrettyDuration.lastDurationHours', + ({ duration }) => `Last ${duration} hour${duration === 1 ? '' : 's'}`, + { duration } + ), + 'h+': useEuiI18n( + 'euiPrettyDuration.nextDurationHours', + ({ duration }) => `Next ${duration} hour${duration === 1 ? '' : 's'}`, + { duration } + ), + d: useEuiI18n( + 'euiPrettyDuration.lastDurationDays', + ({ duration }) => `Last ${duration} day${duration === 1 ? '' : 's'}`, + { duration } + ), + 'd+': useEuiI18n( + 'euiPrettyDuration.nexttDurationDays', + ({ duration }) => `Next ${duration} day${duration === 1 ? '' : 's'}`, + { duration } + ), + w: useEuiI18n( + 'euiPrettyDuration.lastDurationWeeks', + ({ duration }) => `Last ${duration} week${duration === 1 ? '' : 's'}`, + { duration } + ), + 'w+': useEuiI18n( + 'euiPrettyDuration.nextDurationWeeks', + ({ duration }) => `Next ${duration} week${duration === 1 ? '' : 's'}`, + { duration } + ), + M: useEuiI18n( + 'euiPrettyDuration.lastDurationMonths', + ({ duration }) => `Last ${duration} month${duration === 1 ? '' : 's'}`, + { duration } + ), + 'M+': useEuiI18n( + 'euiPrettyDuration.nextDurationMonths', + ({ duration }) => `Next ${duration} month${duration === 1 ? '' : 's'}`, + { duration } + ), + y: useEuiI18n( + 'euiPrettyDuration.lastDurationYears', + ({ duration }) => `Last ${duration} year${duration === 1 ? '' : 's'}`, + { duration } + ), + 'y+': useEuiI18n( + 'euiPrettyDuration.nextDurationYears', + ({ duration }) => `Next ${duration} year${duration === 1 ? '' : 's'}`, + { duration } + ), +}); + +const useRelativeDurationRoundedI18n = (prettyDuration: string) => ({ + s: useEuiI18n( + 'euiPrettyDuration.durationRoundedToSecond', + '{prettyDuration} rounded to the second', + { prettyDuration } + ), + m: useEuiI18n( + 'euiPrettyDuration.durationRoundedToMinute', + '{prettyDuration} rounded to the minute', + { prettyDuration } + ), + h: useEuiI18n( + 'euiPrettyDuration.durationRoundedToHour', + '{prettyDuration} rounded to the hour', + { prettyDuration } + ), + d: useEuiI18n( + 'euiPrettyDuration.durationRoundedToDay', + '{prettyDuration} rounded to the day', + { prettyDuration } + ), + w: useEuiI18n( + 'euiPrettyDuration.durationRoundedToWeek', + '{prettyDuration} rounded to the week', + { prettyDuration } + ), + M: useEuiI18n( + 'euiPrettyDuration.durationRoundedToMonth', + '{prettyDuration} rounded to the month', + { prettyDuration } + ), + y: useEuiI18n( + 'euiPrettyDuration.durationRoundedToYear', + '{prettyDuration} rounded to the year', + { prettyDuration } + ), +}); + +/** + * Reusable format time string util + */ const ISO_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; @@ -70,14 +196,10 @@ export const usePrettyDuration = ({ }: PrettyDurationProps) => { let prettyDuration: string = ''; - const timeOptions = useI18nTimeOptions(); - const { - timeUnits, - timeUnitsPlural, - timeTenseOptions, - commonDurationRanges, - } = timeOptions; - + /** + * If it's a quick range, use the quick range label + */ + const { commonDurationRanges } = useI18nTimeOptions(); const matchingQuickRange = hasRangeMatch( timeFrom, timeTo, @@ -87,39 +209,42 @@ export const usePrettyDuration = ({ prettyDuration = matchingQuickRange.label; } - let relativeDuration = ''; + /** + * Otherwise if it's a relative (possibly rounded) duration, figure out + * a pretty i18n'd duration to display + */ + let relativeDuration: number = 0; let relativeParts = {} as RelativeParts; + if (isRelativeToNow(timeFrom, timeTo)) { - let timeTense; if (getDateMode(timeTo) === DATE_MODES.NOW) { - timeTense = timeTenseOptions[0].text; // Last relativeParts = parseRelativeParts(timeFrom); } else { - timeTense = timeTenseOptions[1].text; // Next relativeParts = parseRelativeParts(timeTo); } + relativeDuration = relativeParts.count; + } - const countTimeUnit = relativeParts.unit.substring(0, 1) as TimeUnitId; - const countTimeUnitFullName = - relativeParts.count > 1 - ? timeUnitsPlural[countTimeUnit] - : timeUnits[countTimeUnit]; + const relativeDurationI18n = useRelativeDurationI18n(relativeDuration); + const relativeDurationString = relativeParts.unit + ? relativeDurationI18n[relativeParts.unit as TimeUnitAllId] + : ''; - relativeDuration = `${timeTense} ${relativeParts.count} ${countTimeUnitFullName}`; - } - const roundedRelativeDuration = useEuiI18n( - 'euiPrettyDuration.roundedRelativeDuration', - '{time} rounded to the {unit}', - { time: relativeDuration, unit: timeUnits[relativeParts.roundUnit!] } + const roundedDurationI18n = useRelativeDurationRoundedI18n( + relativeDurationString ); - if (relativeParts.round && relativeParts.roundUnit) { - relativeDuration = roundedRelativeDuration; - } + const roundedDurationString = + relativeParts.round && relativeParts.roundUnit + ? roundedDurationI18n[relativeParts.roundUnit] + : ''; + if (!prettyDuration) { - prettyDuration = relativeDuration; + prettyDuration = roundedDurationString || relativeDurationString; } - // Can't look up the returned value, display a fallback text + /** + * If it's none of the above, display basic fallback copy + */ const displayFrom = useFormatTimeString(timeFrom, dateFormat); const displayTo = useFormatTimeString(timeTo, dateFormat, true); const fallbackDuration = useEuiI18n( diff --git a/src/components/date_picker/types.ts b/src/components/date_picker/types.ts index a318d3d1f0fd..02a0918dad82 100644 --- a/src/components/date_picker/types.ts +++ b/src/components/date_picker/types.ts @@ -16,6 +16,7 @@ export interface DurationRange { export type TimeUnitId = 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'y'; export type TimeUnitFromNowId = 's+' | 'm+' | 'h+' | 'd+' | 'w+' | 'M+' | 'y+'; +export type TimeUnitAllId = TimeUnitId | TimeUnitFromNowId; export type TimeUnitLabel = | 'second' | 'minute' @@ -54,7 +55,7 @@ export interface RelativeParts { export interface RelativeOption { text: string; - value: TimeUnitId | TimeUnitFromNowId; + value: TimeUnitAllId; } export type OnRefreshChangeProps = { From 10792e72b3abee08286176c0cfb6c71ed4449627 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 24 Mar 2022 15:05:22 -0700 Subject: [PATCH 25/29] Update snapshots --- .../super_date_picker.test.tsx.snap | 424 +++++++----------- .../__snapshots__/quick_select.test.tsx.snap | 4 +- .../quick_select_popover.test.tsx.snap | 53 +-- 3 files changed, 173 insertions(+), 308 deletions(-) diff --git a/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap b/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap index 534e4d81befc..942022815b0b 100644 --- a/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap +++ b/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap @@ -110,24 +110,18 @@ exports[`EuiSuperDatePicker is rendered 1`] = ` ], "refreshUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, ], - "refreshUnitsShorthand": Object { - "d": "d", - "h": "h", - "m": "m", - "s": "s", - }, "relativeOptions": Array [ Object { "text": "Seconds ago", @@ -186,6 +180,15 @@ exports[`EuiSuperDatePicker is rendered 1`] = ` "value": "y+", }, ], + "relativeRoundingLabels": Object { + "M": "Round to the month", + "d": "Round to the day", + "h": "Round to the hour", + "m": "Round to the minute", + "s": "Round to the second", + "w": "Round to the week", + "y": "Round to the year", + }, "timeTenseOptions": Array [ Object { "text": "Last", @@ -196,54 +199,36 @@ exports[`EuiSuperDatePicker is rendered 1`] = ` "value": "next", }, ], - "timeUnits": Object { - "M": "month", - "d": "day", - "h": "hour", - "m": "minute", - "s": "second", - "w": "week", - "y": "year", - }, "timeUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, Object { - "text": "days", + "text": "Days", "value": "d", }, Object { - "text": "weeks", + "text": "Weeks", "value": "w", }, Object { - "text": "months", + "text": "Months", "value": "M", }, Object { - "text": "years", + "text": "Years", "value": "y", }, ], - "timeUnitsPlural": Object { - "M": "months", - "d": "days", - "h": "hours", - "m": "minutes", - "s": "seconds", - "w": "weeks", - "y": "years", - }, } } /> @@ -450,24 +435,18 @@ exports[`EuiSuperDatePicker props accepts data-test-subj and passes to EuiFormCo ], "refreshUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, ], - "refreshUnitsShorthand": Object { - "d": "d", - "h": "h", - "m": "m", - "s": "s", - }, "relativeOptions": Array [ Object { "text": "Seconds ago", @@ -526,6 +505,15 @@ exports[`EuiSuperDatePicker props accepts data-test-subj and passes to EuiFormCo "value": "y+", }, ], + "relativeRoundingLabels": Object { + "M": "Round to the month", + "d": "Round to the day", + "h": "Round to the hour", + "m": "Round to the minute", + "s": "Round to the second", + "w": "Round to the week", + "y": "Round to the year", + }, "timeTenseOptions": Array [ Object { "text": "Last", @@ -536,54 +524,36 @@ exports[`EuiSuperDatePicker props accepts data-test-subj and passes to EuiFormCo "value": "next", }, ], - "timeUnits": Object { - "M": "month", - "d": "day", - "h": "hour", - "m": "minute", - "s": "second", - "w": "week", - "y": "year", - }, "timeUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, Object { - "text": "days", + "text": "Days", "value": "d", }, Object { - "text": "weeks", + "text": "Weeks", "value": "w", }, Object { - "text": "months", + "text": "Months", "value": "M", }, Object { - "text": "years", + "text": "Years", "value": "y", }, ], - "timeUnitsPlural": Object { - "M": "months", - "d": "days", - "h": "hours", - "m": "minutes", - "s": "seconds", - "w": "weeks", - "y": "years", - }, } } /> @@ -790,24 +760,18 @@ exports[`EuiSuperDatePicker props compressed is rendered 1`] = ` ], "refreshUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, ], - "refreshUnitsShorthand": Object { - "d": "d", - "h": "h", - "m": "m", - "s": "s", - }, "relativeOptions": Array [ Object { "text": "Seconds ago", @@ -866,6 +830,15 @@ exports[`EuiSuperDatePicker props compressed is rendered 1`] = ` "value": "y+", }, ], + "relativeRoundingLabels": Object { + "M": "Round to the month", + "d": "Round to the day", + "h": "Round to the hour", + "m": "Round to the minute", + "s": "Round to the second", + "w": "Round to the week", + "y": "Round to the year", + }, "timeTenseOptions": Array [ Object { "text": "Last", @@ -876,54 +849,36 @@ exports[`EuiSuperDatePicker props compressed is rendered 1`] = ` "value": "next", }, ], - "timeUnits": Object { - "M": "month", - "d": "day", - "h": "hour", - "m": "minute", - "s": "second", - "w": "week", - "y": "year", - }, "timeUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, Object { - "text": "days", + "text": "Days", "value": "d", }, Object { - "text": "weeks", + "text": "Weeks", "value": "w", }, Object { - "text": "months", + "text": "Months", "value": "M", }, Object { - "text": "years", + "text": "Years", "value": "y", }, ], - "timeUnitsPlural": Object { - "M": "months", - "d": "days", - "h": "hours", - "m": "minutes", - "s": "seconds", - "w": "weeks", - "y": "years", - }, } } /> @@ -1166,24 +1121,18 @@ exports[`EuiSuperDatePicker props isQuickSelectOnly is rendered 1`] = ` ], "refreshUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, ], - "refreshUnitsShorthand": Object { - "d": "d", - "h": "h", - "m": "m", - "s": "s", - }, "relativeOptions": Array [ Object { "text": "Seconds ago", @@ -1242,6 +1191,15 @@ exports[`EuiSuperDatePicker props isQuickSelectOnly is rendered 1`] = ` "value": "y+", }, ], + "relativeRoundingLabels": Object { + "M": "Round to the month", + "d": "Round to the day", + "h": "Round to the hour", + "m": "Round to the minute", + "s": "Round to the second", + "w": "Round to the week", + "y": "Round to the year", + }, "timeTenseOptions": Array [ Object { "text": "Last", @@ -1252,54 +1210,36 @@ exports[`EuiSuperDatePicker props isQuickSelectOnly is rendered 1`] = ` "value": "next", }, ], - "timeUnits": Object { - "M": "month", - "d": "day", - "h": "hour", - "m": "minute", - "s": "second", - "w": "week", - "y": "year", - }, "timeUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, Object { - "text": "days", + "text": "Days", "value": "d", }, Object { - "text": "weeks", + "text": "Weeks", "value": "w", }, Object { - "text": "months", + "text": "Months", "value": "M", }, Object { - "text": "years", + "text": "Years", "value": "y", }, ], - "timeUnitsPlural": Object { - "M": "months", - "d": "days", - "h": "hours", - "m": "minutes", - "s": "seconds", - "w": "weeks", - "y": "years", - }, } } /> @@ -1440,24 +1380,18 @@ exports[`EuiSuperDatePicker props showUpdateButton can be false 1`] = ` ], "refreshUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, ], - "refreshUnitsShorthand": Object { - "d": "d", - "h": "h", - "m": "m", - "s": "s", - }, "relativeOptions": Array [ Object { "text": "Seconds ago", @@ -1516,6 +1450,15 @@ exports[`EuiSuperDatePicker props showUpdateButton can be false 1`] = ` "value": "y+", }, ], + "relativeRoundingLabels": Object { + "M": "Round to the month", + "d": "Round to the day", + "h": "Round to the hour", + "m": "Round to the minute", + "s": "Round to the second", + "w": "Round to the week", + "y": "Round to the year", + }, "timeTenseOptions": Array [ Object { "text": "Last", @@ -1526,54 +1469,36 @@ exports[`EuiSuperDatePicker props showUpdateButton can be false 1`] = ` "value": "next", }, ], - "timeUnits": Object { - "M": "month", - "d": "day", - "h": "hour", - "m": "minute", - "s": "second", - "w": "week", - "y": "year", - }, "timeUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, Object { - "text": "days", + "text": "Days", "value": "d", }, Object { - "text": "weeks", + "text": "Weeks", "value": "w", }, Object { - "text": "months", + "text": "Months", "value": "M", }, Object { - "text": "years", + "text": "Years", "value": "y", }, ], - "timeUnitsPlural": Object { - "M": "months", - "d": "days", - "h": "hours", - "m": "minutes", - "s": "seconds", - "w": "weeks", - "y": "years", - }, } } /> @@ -1758,24 +1683,18 @@ exports[`EuiSuperDatePicker props showUpdateButton can be iconOnly 1`] = ` ], "refreshUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, ], - "refreshUnitsShorthand": Object { - "d": "d", - "h": "h", - "m": "m", - "s": "s", - }, "relativeOptions": Array [ Object { "text": "Seconds ago", @@ -1834,6 +1753,15 @@ exports[`EuiSuperDatePicker props showUpdateButton can be iconOnly 1`] = ` "value": "y+", }, ], + "relativeRoundingLabels": Object { + "M": "Round to the month", + "d": "Round to the day", + "h": "Round to the hour", + "m": "Round to the minute", + "s": "Round to the second", + "w": "Round to the week", + "y": "Round to the year", + }, "timeTenseOptions": Array [ Object { "text": "Last", @@ -1844,54 +1772,36 @@ exports[`EuiSuperDatePicker props showUpdateButton can be iconOnly 1`] = ` "value": "next", }, ], - "timeUnits": Object { - "M": "month", - "d": "day", - "h": "hour", - "m": "minute", - "s": "second", - "w": "week", - "y": "year", - }, "timeUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, Object { - "text": "days", + "text": "Days", "value": "d", }, Object { - "text": "weeks", + "text": "Weeks", "value": "w", }, Object { - "text": "months", + "text": "Months", "value": "M", }, Object { - "text": "years", + "text": "Years", "value": "y", }, ], - "timeUnitsPlural": Object { - "M": "months", - "d": "days", - "h": "hours", - "m": "minutes", - "s": "seconds", - "w": "weeks", - "y": "years", - }, } } /> @@ -2097,24 +2007,18 @@ exports[`EuiSuperDatePicker props width can be auto 1`] = ` ], "refreshUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, ], - "refreshUnitsShorthand": Object { - "d": "d", - "h": "h", - "m": "m", - "s": "s", - }, "relativeOptions": Array [ Object { "text": "Seconds ago", @@ -2173,6 +2077,15 @@ exports[`EuiSuperDatePicker props width can be auto 1`] = ` "value": "y+", }, ], + "relativeRoundingLabels": Object { + "M": "Round to the month", + "d": "Round to the day", + "h": "Round to the hour", + "m": "Round to the minute", + "s": "Round to the second", + "w": "Round to the week", + "y": "Round to the year", + }, "timeTenseOptions": Array [ Object { "text": "Last", @@ -2183,54 +2096,36 @@ exports[`EuiSuperDatePicker props width can be auto 1`] = ` "value": "next", }, ], - "timeUnits": Object { - "M": "month", - "d": "day", - "h": "hour", - "m": "minute", - "s": "second", - "w": "week", - "y": "year", - }, "timeUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, Object { - "text": "days", + "text": "Days", "value": "d", }, Object { - "text": "weeks", + "text": "Weeks", "value": "w", }, Object { - "text": "months", + "text": "Months", "value": "M", }, Object { - "text": "years", + "text": "Years", "value": "y", }, ], - "timeUnitsPlural": Object { - "M": "months", - "d": "days", - "h": "hours", - "m": "minutes", - "s": "seconds", - "w": "weeks", - "y": "years", - }, } } /> @@ -2436,24 +2331,18 @@ exports[`EuiSuperDatePicker props width can be full 1`] = ` ], "refreshUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, ], - "refreshUnitsShorthand": Object { - "d": "d", - "h": "h", - "m": "m", - "s": "s", - }, "relativeOptions": Array [ Object { "text": "Seconds ago", @@ -2512,6 +2401,15 @@ exports[`EuiSuperDatePicker props width can be full 1`] = ` "value": "y+", }, ], + "relativeRoundingLabels": Object { + "M": "Round to the month", + "d": "Round to the day", + "h": "Round to the hour", + "m": "Round to the minute", + "s": "Round to the second", + "w": "Round to the week", + "y": "Round to the year", + }, "timeTenseOptions": Array [ Object { "text": "Last", @@ -2522,54 +2420,36 @@ exports[`EuiSuperDatePicker props width can be full 1`] = ` "value": "next", }, ], - "timeUnits": Object { - "M": "month", - "d": "day", - "h": "hour", - "m": "minute", - "s": "second", - "w": "week", - "y": "year", - }, "timeUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, Object { - "text": "days", + "text": "Days", "value": "d", }, Object { - "text": "weeks", + "text": "Weeks", "value": "w", }, Object { - "text": "months", + "text": "Months", "value": "M", }, Object { - "text": "years", + "text": "Years", "value": "y", }, ], - "timeUnitsPlural": Object { - "M": "months", - "d": "days", - "h": "hours", - "m": "minutes", - "s": "seconds", - "w": "weeks", - "y": "years", - }, } } /> diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select.test.tsx.snap b/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select.test.tsx.snap index ca349c98328e..01703e056e2a 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select.test.tsx.snap +++ b/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select.test.tsx.snap @@ -113,7 +113,7 @@ exports[`EuiQuickSelect is rendered 1`] = ` values={ Object { "timeTense": "last", - "timeUnit": "minutes", + "timeUnit": "Minutes", "timeValue": 15, } } @@ -236,7 +236,7 @@ exports[`EuiQuickSelect prevQuickSelect 1`] = ` values={ Object { "timeTense": "Next", - "timeUnit": "months", + "timeUnit": "Months", "timeValue": 32, } } diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap b/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap index 35fedb8d88fd..5b5f295bcf7d 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap +++ b/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap @@ -86,24 +86,18 @@ exports[`EuiQuickSelectPopover is rendered 1`] = ` ], "refreshUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, ], - "refreshUnitsShorthand": Object { - "d": "d", - "h": "h", - "m": "m", - "s": "s", - }, "relativeOptions": Array [ Object { "text": "Seconds ago", @@ -162,6 +156,15 @@ exports[`EuiQuickSelectPopover is rendered 1`] = ` "value": "y+", }, ], + "relativeRoundingLabels": Object { + "M": "Round to the month", + "d": "Round to the day", + "h": "Round to the hour", + "m": "Round to the minute", + "s": "Round to the second", + "w": "Round to the week", + "y": "Round to the year", + }, "timeTenseOptions": Array [ Object { "text": "Last", @@ -172,54 +175,36 @@ exports[`EuiQuickSelectPopover is rendered 1`] = ` "value": "next", }, ], - "timeUnits": Object { - "M": "month", - "d": "day", - "h": "hour", - "m": "minute", - "s": "second", - "w": "week", - "y": "year", - }, "timeUnitsOptions": Array [ Object { - "text": "seconds", + "text": "Seconds", "value": "s", }, Object { - "text": "minutes", + "text": "Minutes", "value": "m", }, Object { - "text": "hours", + "text": "Hours", "value": "h", }, Object { - "text": "days", + "text": "Days", "value": "d", }, Object { - "text": "weeks", + "text": "Weeks", "value": "w", }, Object { - "text": "months", + "text": "Months", "value": "M", }, Object { - "text": "years", + "text": "Years", "value": "y", }, ], - "timeUnitsPlural": Object { - "M": "months", - "d": "days", - "h": "hours", - "m": "minutes", - "s": "seconds", - "w": "weeks", - "y": "years", - }, } } /> From d53c0a28ecbdc4defc303a42da12425614edb46f Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 24 Mar 2022 15:52:16 -0700 Subject: [PATCH 26/29] Fix broken i18n behavior - with `tokens` arrays was not passing an i18nMappingFunc - i18n tokens that passed a function were not passing the rendered function output to the i18nMappingFunc - Updated the i18n documentation page to show both of these examples now working with the babelfish toggled on --- src-docs/src/views/i18n/i18n_interpolation.js | 31 ++++++++++++++++++- src/components/i18n/i18n.tsx | 6 +++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src-docs/src/views/i18n/i18n_interpolation.js b/src-docs/src/views/i18n/i18n_interpolation.js index 4c830cc37c5c..1e5c90dfdf88 100644 --- a/src-docs/src/views/i18n/i18n_interpolation.js +++ b/src-docs/src/views/i18n/i18n_interpolation.js @@ -10,7 +10,7 @@ import { } from '../../../../src/components'; export default () => { - const [count, setCount] = useState(1); + const [count, setCount] = useState(0); return ( <> @@ -44,6 +44,35 @@ export default () => { + +

useEuiI18n with function interpolation

+
+

+ {useEuiI18n( + 'euiI18nInterpolation.clickedCount', + ({ count }) => + `Clicked on button ${count} time${count === 1 ? '' : 's'}.`, + { count } + )} +

+ + + + +

EuiI18n with function interpolation

+
+

+ + `Clicked on button ${count} time${count === 1 ? '' : 's'}.` + } + values={{ count }} + /> +

+ + +

useEuiI18n with component interpolation

diff --git a/src/components/i18n/i18n.tsx b/src/components/i18n/i18n.tsx index b8c9a82e4f00..032ecff6a369 100644 --- a/src/components/i18n/i18n.tsx +++ b/src/components/i18n/i18n.tsx @@ -61,7 +61,10 @@ function lookupToken< return errorOnMissingValues(token); } // @ts-ignore TypeScript complains that `DEFAULT` doesn't have a call signature but we verified `renderable` is a function - return renderable(values); + const rendered = renderable(values); + return (i18nMappingFunc + ? i18nMappingFunc(rendered as string) + : rendered) as RESOLVED; } else if (values === undefined || typeof renderable !== 'string') { if (i18nMappingFunc && typeof valueDefault === 'string') { renderable = i18nMappingFunc(valueDefault); @@ -133,6 +136,7 @@ const EuiI18n = < lookupToken({ token, i18nMapping: mapping, + i18nMappingFunc: mappingFunc, valueDefault: props.defaults[idx], render, }) From 238686cd844b4ecc2c3a771d817cedd59322e1b3 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 24 Mar 2022 16:01:31 -0700 Subject: [PATCH 27/29] Fix testCustomHook move - moved in latest main --- .../date_picker/super_date_picker/pretty_duration.test.tsx | 2 +- .../date_picker/super_date_picker/pretty_interval.test.ts | 2 +- .../date_picker/super_date_picker/time_options.test.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/date_picker/super_date_picker/pretty_duration.test.tsx b/src/components/date_picker/super_date_picker/pretty_duration.test.tsx index a05b3d0dc5ac..a3818586f4a6 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.test.tsx +++ b/src/components/date_picker/super_date_picker/pretty_duration.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { testCustomHook } from '../../../test/test_custom_hook.test_helper'; +import { testCustomHook } from '../../../test/internal'; import { usePrettyDuration, diff --git a/src/components/date_picker/super_date_picker/pretty_interval.test.ts b/src/components/date_picker/super_date_picker/pretty_interval.test.ts index 364768a58329..fb0d93399662 100644 --- a/src/components/date_picker/super_date_picker/pretty_interval.test.ts +++ b/src/components/date_picker/super_date_picker/pretty_interval.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { testCustomHook } from '../../../test/test_custom_hook.test_helper'; +import { testCustomHook } from '../../../test/internal'; import { usePrettyInterval } from './pretty_interval'; diff --git a/src/components/date_picker/super_date_picker/time_options.test.tsx b/src/components/date_picker/super_date_picker/time_options.test.tsx index 092ccb755e95..27c70bb3c172 100644 --- a/src/components/date_picker/super_date_picker/time_options.test.tsx +++ b/src/components/date_picker/super_date_picker/time_options.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { testCustomHook } from '../../../test/test_custom_hook.test_helper'; +import { testCustomHook } from '../../../test/internal'; import { useI18nTimeOptions, RenderI18nTimeOptions } from './time_options'; From 1dc826d52f7789562c70d6692bd6b896efa99ca9 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Mon, 28 Mar 2022 08:19:21 -0700 Subject: [PATCH 28/29] Revert "Fix broken i18n behavior" This reverts commit d53c0a28ecbdc4defc303a42da12425614edb46f. --- src-docs/src/views/i18n/i18n_interpolation.js | 31 +------------------ src/components/i18n/i18n.tsx | 6 +--- 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/src-docs/src/views/i18n/i18n_interpolation.js b/src-docs/src/views/i18n/i18n_interpolation.js index 1e5c90dfdf88..4c830cc37c5c 100644 --- a/src-docs/src/views/i18n/i18n_interpolation.js +++ b/src-docs/src/views/i18n/i18n_interpolation.js @@ -10,7 +10,7 @@ import { } from '../../../../src/components'; export default () => { - const [count, setCount] = useState(0); + const [count, setCount] = useState(1); return ( <> @@ -44,35 +44,6 @@ export default () => { - -

useEuiI18n with function interpolation

-
-

- {useEuiI18n( - 'euiI18nInterpolation.clickedCount', - ({ count }) => - `Clicked on button ${count} time${count === 1 ? '' : 's'}.`, - { count } - )} -

- - - - -

EuiI18n with function interpolation

-
-

- - `Clicked on button ${count} time${count === 1 ? '' : 's'}.` - } - values={{ count }} - /> -

- - -

useEuiI18n with component interpolation

diff --git a/src/components/i18n/i18n.tsx b/src/components/i18n/i18n.tsx index 032ecff6a369..b8c9a82e4f00 100644 --- a/src/components/i18n/i18n.tsx +++ b/src/components/i18n/i18n.tsx @@ -61,10 +61,7 @@ function lookupToken< return errorOnMissingValues(token); } // @ts-ignore TypeScript complains that `DEFAULT` doesn't have a call signature but we verified `renderable` is a function - const rendered = renderable(values); - return (i18nMappingFunc - ? i18nMappingFunc(rendered as string) - : rendered) as RESOLVED; + return renderable(values); } else if (values === undefined || typeof renderable !== 'string') { if (i18nMappingFunc && typeof valueDefault === 'string') { renderable = i18nMappingFunc(valueDefault); @@ -136,7 +133,6 @@ const EuiI18n = < lookupToken({ token, i18nMapping: mapping, - i18nMappingFunc: mappingFunc, valueDefault: props.defaults[idx], render, }) From 6bc7035beccec297e660c5c00bb18704150abdfa Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Mon, 28 Mar 2022 08:19:25 -0700 Subject: [PATCH 29/29] Revert "[REVERT] Test moment locale" This reverts commit f28d90245be1d519458aa41b34ddf1f5ab3d6ac7. --- src-docs/src/views/super_date_picker/super_date_picker.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src-docs/src/views/super_date_picker/super_date_picker.tsx b/src-docs/src/views/super_date_picker/super_date_picker.tsx index 6774d7dd889d..c2575fe0da07 100644 --- a/src-docs/src/views/super_date_picker/super_date_picker.tsx +++ b/src-docs/src/views/super_date_picker/super_date_picker.tsx @@ -90,7 +90,6 @@ export default () => { end={end} onTimeChange={onTimeChange} onRefresh={onRefresh} - locale="ja" /> );