From b8bc8caf4ee740bc498b39f76d89b3ca836ff621 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 4 Dec 2018 10:59:10 -0700 Subject: [PATCH 01/37] super date picker component --- .../views/date_picker/date_picker_example.js | 19 ++ .../views/date_picker/super_date_picker.js | 33 ++ src/components/date_picker/_index.scss | 1 + src/components/date_picker/index.js | 4 + .../super_date_picker/_super_date_picker.scss | 95 ++++++ .../super_date_picker/absolute_form.js | 89 +++++ .../super_date_picker/date_button.js | 165 +++++++++ .../super_date_picker/quick_select_popover.js | 319 ++++++++++++++++++ .../super_date_picker/super_date_picker.js | 194 +++++++++++ src/components/index.js | 1 + 10 files changed, 920 insertions(+) create mode 100644 src-docs/src/views/date_picker/super_date_picker.js create mode 100644 src/components/date_picker/super_date_picker/_super_date_picker.scss create mode 100644 src/components/date_picker/super_date_picker/absolute_form.js create mode 100644 src/components/date_picker/super_date_picker/date_button.js create mode 100644 src/components/date_picker/super_date_picker/quick_select_popover.js create mode 100644 src/components/date_picker/super_date_picker/super_date_picker.js diff --git a/src-docs/src/views/date_picker/date_picker_example.js b/src-docs/src/views/date_picker/date_picker_example.js index 38dcff6a3f9..7235470eee8 100644 --- a/src-docs/src/views/date_picker/date_picker_example.js +++ b/src-docs/src/views/date_picker/date_picker_example.js @@ -58,6 +58,10 @@ import Utc from './utc'; const utcSource = require('!!raw-loader!./utc'); const utcHtml = renderToHtml(Utc); +import SuperDatePicker from './super_date_picker'; +const superDatePickerSource = require('!!raw-loader!./super_date_picker'); +const superDatePickerHtml = renderToHtml(SuperDatePicker); + import GlobalDatePicker from './global_date_picker'; const globalDatePickerSource = require('!!raw-loader!./global_date_picker'); const globalDatePickerHtml = renderToHtml(GlobalDatePicker); @@ -265,6 +269,21 @@ export const DatePickerExample = { ), demo: , + }, { + title: 'Super date picker', + source: [{ + type: GuideSectionTypes.JS, + code: superDatePickerSource, + }, { + type: GuideSectionTypes.HTML, + code: superDatePickerHtml, + }], + text: ( +
+ Super date picker +
+ ), + demo: , }, { title: 'Global date picker', source: [{ diff --git a/src-docs/src/views/date_picker/super_date_picker.js b/src-docs/src/views/date_picker/super_date_picker.js new file mode 100644 index 00000000000..5352288f53f --- /dev/null +++ b/src-docs/src/views/date_picker/super_date_picker.js @@ -0,0 +1,33 @@ + +import React, { Component } from 'react'; + +import { + EuiSuperDatePicker, +} from '../../../../src/components'; + +export default class extends Component { + + state = {} + + onTimeChange = ({ from, to }) => { + this.setState({ from, to }); + } + + onRefreshChange = ({ isPaused, refreshInterval }) => { + this.setState({ isPaused, refreshInterval }); + } + + render() { + + return ( + + ); + } +} diff --git a/src/components/date_picker/_index.scss b/src/components/date_picker/_index.scss index 65219322767..cc5927780e8 100644 --- a/src/components/date_picker/_index.scss +++ b/src/components/date_picker/_index.scss @@ -1,3 +1,4 @@ // Uses some form mixins @import 'date_picker'; @import 'date_picker_range'; +@import 'super_date_picker/super_date_picker'; diff --git a/src/components/date_picker/index.js b/src/components/date_picker/index.js index eb720402eec..c7087098446 100644 --- a/src/components/date_picker/index.js +++ b/src/components/date_picker/index.js @@ -5,3 +5,7 @@ export { export { EuiDatePickerRange, } from './date_picker_range'; + +export { + EuiSuperDatePicker, +} from './super_date_picker/super_date_picker'; diff --git a/src/components/date_picker/super_date_picker/_super_date_picker.scss b/src/components/date_picker/super_date_picker/_super_date_picker.scss new file mode 100644 index 00000000000..b8b48560225 --- /dev/null +++ b/src/components/date_picker/super_date_picker/_super_date_picker.scss @@ -0,0 +1,95 @@ +// sass-lint:disable no-important +.euiSuperDatePicker__quickSelectButton { + // Override prepend border since button already lives inside another prepend + border-right: none !important; + + .euiSuperDatePicker__quickSelectButtonText { + // Override specificity from universal and sibiling selectors + margin-right: $euiSizeXS !important; + } +} + +.euiSuperDatePicker.euiFormControlLayout { + max-width: 480px; + + > .euiFormControlLayout__childrenWrapper { + flex: 1 1 100%; + overflow: hidden; + + > .euiDatePickerRange { + max-width: none; + width: auto; + + // sass-lint:disable nesting-depth + .euiPopover__anchor { + display: block; + } + } + } +} + +.euiSuperDatePicker__dateButton { + @include euiFormControlText; + display: block; + width: 100%; + padding: 0 $euiSizeS; + line-height: $euiFormControlHeight - 2px; + height: $euiFormControlHeight - 2px; + word-break: break-all; + transition: background $euiAnimSpeedFast ease-in; + + $backgroundColor: tintOrShade($euiColorSuccess, 90%, 70%); + $textColor: makeHighContrastColor($euiColorSuccess, $backgroundColor); + + &-isSelected, + &-needsUpdating, + &:hover, + &:focus { + background-color: $backgroundColor; + } + + &-needsUpdating { + color: $textColor; + } + + &-isInvalid { + $backgroundColor: tintOrShade($euiColorDanger, 90%, 70%); + $textColor: makeHighContrastColor($euiColorDanger, $backgroundColor); + background-color: $backgroundColor; + color: $textColor; + } + + .euiFormControlLayout__prepend { + border: none; + } +} + +.euiSuperDatePicker__dateButton--start { + text-align: right; +} + +.euiSuperDatePicker__dateButton--end { + text-align: left; +} + +.euiSuperDatePicker__updateButton { + // Just wide enough for all 3 states + min-width: $euiButtonMinWidth + ($euiSizeXS * 1.5); +} + +.euiSuperDatePicker__popoverSection { + @include euiScrollBar; + max-height: $euiSizeM * 11; + overflow: hidden; + overflow-y: auto; +} + +@include euiBreakpoint('xs', 's') { + .euiSuperDatePicker__updateButton { + min-width: 0; + + .euiSuperDatePicker__updateButtonText { + display: none; + } + } +} diff --git a/src/components/date_picker/super_date_picker/absolute_form.js b/src/components/date_picker/super_date_picker/absolute_form.js new file mode 100644 index 00000000000..90497289357 --- /dev/null +++ b/src/components/date_picker/super_date_picker/absolute_form.js @@ -0,0 +1,89 @@ + +import PropTypes from 'prop-types'; +import React, { Component, Fragment } from 'react'; + +import moment from 'moment'; + +import dateMath from '@kbn/datemath'; + +import { + EuiDatePicker, + EuiFieldText, + EuiFormRow, +} from '@elastic/eui'; + +const INPUT_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSS'; + +const toMoment = (value, roundUp) => { + const valueAsMoment = dateMath.parse(value, { roundUp }); + return { + value: valueAsMoment, + textInputValue: valueAsMoment.format(INPUT_DATE_FORMAT) + }; +}; + +export class AbsoluteForm extends Component { + + constructor(props) { + super(props); + + this.state = { + ...toMoment(this.props.value, this.props.roundUp), + isTextInvalid: false, + }; + } + + static getDerivedStateFromProps = (nextProps) => { + return { + ...toMoment(nextProps.value, nextProps.roundUp), + isTextInvalid: false, + }; + } + + handleChange = (date) => { + this.props.onChange(date.toISOString()); + } + + handleTextChange = (evt) => { + const date = moment(evt.target.value, INPUT_DATE_FORMAT, true); + if (date.isValid()) { + this.props.onChange(date.toISOString()); + } + + this.setState({ + textInputValue: evt.target.value, + isTextInvalid: !date.isValid() + }); + } + + render() { + return ( + + + + + + + ); + } +} + +AbsoluteForm.propTypes = { + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + roundUp: PropTypes.bool.isRequired, +}; diff --git a/src/components/date_picker/super_date_picker/date_button.js b/src/components/date_picker/super_date_picker/date_button.js new file mode 100644 index 00000000000..0c8d7aded15 --- /dev/null +++ b/src/components/date_picker/super_date_picker/date_button.js @@ -0,0 +1,165 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import chrome from 'ui/chrome'; +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { AbsoluteForm } from './absolute_form'; +import { RelativeForm } from './relative_form'; + +import { + EuiPopover, + EuiTabbedContent, + EuiText, +} from '@elastic/eui'; + +import { formatTimeString } from '../pretty_duration'; +import { + getTimeMode, + TIME_MODES, + toAbsoluteString, + toRelativeString, +} from '../lib/time_modes'; + +export class DateButton extends Component { + + constructor(props) { + super(props); + + this.state = { + isOpen: false, + }; + } + + onTabClick = (selectedTab) => { + const { + value, + roundUp + } = this.props; + + switch(selectedTab.id) { + case TIME_MODES.ABSOLUTE: + this.props.onChange(toAbsoluteString(value, roundUp)); + break; + case TIME_MODES.RELATIVE: + this.props.onChange(toRelativeString(value)); + break; + case TIME_MODES.NOW: + this.props.onChange('now'); + break; + } + }; + + closePopover = () => { + this.setState({ isOpen: false }); + } + + togglePopover = () => { + this.setState((prevState) => ({ + isOpen: !prevState.isOpen, + })); + } + + renderTabs = () => { + return [ + { + id: TIME_MODES.ABSOLUTE, + name: 'Absolute', + content: ( + + ), + }, + { + id: TIME_MODES.RELATIVE, + name: 'Relative', + content: ( + + ), + }, + { + id: TIME_MODES.NOW, + name: 'Now', + content: ( + + ), + } + ]; + } + + render() { + const input = ( + + ); + + return ( + + + + + + + ); + } +} + +DateButton.propTypes = { + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + roundUp: PropTypes.bool, +}; + +DateButton.defaultProps = { + roundUp: false, +}; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover.js b/src/components/date_picker/super_date_picker/quick_select_popover.js new file mode 100644 index 00000000000..ed4926d7dc3 --- /dev/null +++ b/src/components/date_picker/super_date_picker/quick_select_popover.js @@ -0,0 +1,319 @@ +import _ from 'lodash'; +import PropTypes from 'prop-types'; +import React, { Component, Fragment } from 'react'; + +/*import { timeUnits } from '../time_units'; +import { timeHistory } from '../../timefilter/time_history'; +import { prettyDuration } from '../pretty_duration'; +import { RefreshIntervalForm } from './refresh_interval_form';*/ + +import { + EuiButtonEmpty, + EuiButton, + EuiButtonIcon, +} from '../../button'; + +import { + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, +} from '../../flex'; + +import { + EuiIcon, +} from '../../icon'; + +import { + EuiPopover, +} from '../../popover'; + +import { + EuiTitle, +} from '../../title'; + +import { + EuiSpacer, +} from '../../spacer'; + +import { + EuiFormRow, + EuiSelect, + EuiFieldNumber, +} from '../../form'; + +import { + EuiText, +} from '../../text'; + +import { + EuiHorizontalRule, +} from '../../horizontal_rule'; + +import { + EuiLink, +} from '../../link'; + +const LAST = 'last'; +const NEXT = 'next'; + +const timeTenseOptions = [ + { value: LAST, text: 'Last' }, + { value: NEXT, text: 'Next' }, +]; +/*const timeUnitsOptions = Object.keys(timeUnits).map(key => { + return { value: key, text: `${timeUnits[key]}s` }; +});*/ + +export class QuickSelectPopover extends Component { + + state = { + isOpen: false, + timeTense: LAST, + timeValue: 15, + timeUnits: 'm', + } + + closePopover = () => { + this.setState({ isOpen: false }); + } + + togglePopover = () => { + this.setState((prevState) => ({ + isOpen: !prevState.isOpen + })); + } + + onTimeTenseChange = (evt) => { + this.setState({ + timeTense: evt.target.value, + }); + } + + onTimeValueChange = (evt) => { + const sanitizedValue = parseInt(evt.target.value, 10); + this.setState({ + timeValue: isNaN(sanitizedValue) ? '' : sanitizedValue, + }); + } + + onTimeUnitsChange = (evt) => { + this.setState({ + timeUnits: evt.target.value, + }); + } + + applyQuickSelect = () => { + const { + timeTense, + timeValue, + timeUnits, + } = this.state; + + if (timeTense === NEXT) { + this.setTime({ + from: 'now', + to: `now+${timeValue}${timeUnits}` + }); + return; + } + + this.setTime({ + from: `now-${timeValue}${timeUnits}`, + to: 'now' + }); + } + + setTime = ({ from, to }) => { + this.props.setTime({ + from: from, + to: to + }); + this.closePopover(); + } + + applyTime = ({ from, to }) => { + this.props.applyTime({ + from: from, + to: to + }); + this.closePopover(); + } + + renderTimeNavigation = () => { + return ( + + + + + ); + } + + renderQuickSelect = () => { + return ( + + Quick select + + + + + + + + + + + + + + + + + + + + + Apply + + + + + + ); + } + + renderCommonlyUsed = () => { + const commonlyUsed = chrome.getUiSettingsClient().get('timepicker:quickRanges'); + const sections = _.groupBy(commonlyUsed, 'section'); + + const renderSectionItems = (section) => { + return section.map(({ from, to, display }) => { + const applyTime = () => { + this.applyTime({ from, to }); + }; + return ( + + {display} + + ); + }); + }; + + return ( + + Commonly used + + + {Object.keys(sections).map((key, index) => { + const isLastSection = Object.keys(sections).length - 1 === index; + const sectionSpacer = isLastSection + ? undefined + : (); + return ( + + + {renderSectionItems(sections[key])} + + {sectionSpacer} + + ); + })} + + + ); + } + + renderRecentlyUsed = () => { + const links = timeHistory.get().map(({ from, to }) => { + const applyTime = () => { + this.applyTime({ from, to }); + }; + const display = prettyDuration(from, to, (...args) => chrome.getUiSettingsClient().get(...args)); + return ( + + {display} + + ); + }); + + return ( + + Recently used date ranges + + + + {links} + + + + ); + } + + render() { + const quickSelectButton = ( + + + + ); + + return ( + +
+ Quick select +
+
+ ); + } +} + +QuickSelectPopover.propTypes = { + applyTime: PropTypes.func.isRequired, + setTime: PropTypes.func.isRequired, + setRefresh: PropTypes.func.isRequired, + isPaused: PropTypes.bool.isRequired, + refreshInterval: PropTypes.number, +}; diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js new file mode 100644 index 00000000000..6dd22207426 --- /dev/null +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -0,0 +1,194 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import moment from 'moment'; +import PropTypes from 'prop-types'; +import React, { Component, Fragment } from 'react'; + +/*import dateMath from '@kbn/datemath';*/ + +import { QuickSelectPopover } from './quick_select_popover'; +//import { TimeInput } from './time_input'; + +import { + EuiFormControlLayout, +} from '../../form'; + +import { + EuiText, +} from '../../text'; + +import { + EuiButton, +} from '../../button'; + +import { + EuiFlexGroup, + EuiFlexItem, +} from '../../flex'; + +/*import { prettyDuration } from '../pretty_duration'; +import { timeNavigation } from '../time_navigation'; +import { calculateBounds } from 'ui/timefilter/get_time';*/ + +export class EuiSuperDatePicker extends Component { + + constructor(props) { + super(props); + + this.state = { + from: this.props.from, + to: this.props.to, + isInvalid: false, + hasChanged: false, + isEditMode: false, + }; + } + + static getDerivedStateFromProps = (nextProps) => { + return { + from: nextProps.from, + to: nextProps.to, + isInvalid: false, + hasChanged: false, + isEditMode: false, + }; + } + + setTime = ({ from, to }) => { + // TODO validation once dateMath is an npm artifact + /*const fromMoment = dateMath.parse(from); + const toMoment = dateMath.parse(to, { roundUp: true }); + const isInvalid = fromMoment.isAfter(toMoment); + if (isInvalid) { + this.lastToast = toastNotifications.addDanger({ + title: `Invalid time range`, + text: `From must occur before To`, + }); + }*/ + + this.setState({ + from, + to, + isInvalid: false, + hasChanged: true, + }); + } + + setFrom = (from) => { + this.setTime({ from, to: this.state.to }); + } + + setTo = (to) => { + this.setTime({ from: this.state.from, to }); + } + + /*getBounds = () => { + return calculateBounds({ from: this.state.from, to: this.state.to }); + } + + stepForward = () => { + this.setTime(timeNavigation.stepForward(this.getBounds())); + } + + stepBackward = () => { + this.setTime(timeNavigation.stepBackward(this.getBounds())); + }*/ + + applyTime = () => { + this.props.onTimeChange(this.state.from, this.state.to); + } + + applyQuickTime = ({ from, to }) => { + this.props.onTimeChange(from, to); + } + + renderDateRange = () => { + return; + } + + renderUpdateButton = () => { + const color = this.state.hasChanged ? 'secondary' : 'primary'; + const icon = this.state.hasChanged ? 'kqlFunction' : 'refresh'; + let text = this.state.hasChanged ? 'Update' : 'Refresh'; + + return ( + + {text} + + ); + } + + render() { + const quickSelect = ( + + ); + return ( + + + + + {this.renderDateRange()} + + + + + {this.renderUpdateButton()} + + + + ); + } +} + +EuiSuperDatePicker.propTypes = { + from: PropTypes.string, + to: PropTypes.string, + onTimeChange: PropTypes.func.isRequired, + isPaused: PropTypes.bool, + refreshInterval: PropTypes.number, + onRefreshChange: PropTypes.func.isRequired, +}; + +EuiSuperDatePicker.defaultProps = { + from: 'now-15m', + to: 'now', + isPaused: true, + refreshInterval: 0, +}; + diff --git a/src/components/index.js b/src/components/index.js index c09b340e33d..c724b6ced32 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -72,6 +72,7 @@ export { export { EuiDatePicker, EuiDatePickerRange, + EuiSuperDatePicker, } from './date_picker'; export { From fcf1d8ffb71c91396b3bf52a24b5f4a0310ab48b Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 4 Dec 2018 12:10:07 -0700 Subject: [PATCH 02/37] quick select --- package.json | 1 + .../quick_select_popover/quick_select.js | 141 +++++++++++++++++ .../quick_select_popover.js | 145 ++---------------- .../super_date_picker/super_date_picker.js | 18 +-- .../super_date_picker/time_units.js | 11 ++ yarn.lock | 15 +- 6 files changed, 188 insertions(+), 143 deletions(-) create mode 100644 src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js rename src/components/date_picker/super_date_picker/{ => quick_select_popover}/quick_select_popover.js (58%) create mode 100644 src/components/date_picker/super_date_picker/time_units.js diff --git a/package.json b/package.json index 6d28ba8a366..c6f62723c1c 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "url": "https://github.com/elastic/eui.git" }, "dependencies": { + "@elastic/datemath": "5.0.0", "classnames": "^2.2.5", "core-js": "^2.5.1", "focus-trap-react": "^3.0.4", diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js new file mode 100644 index 00000000000..4f80f2ca6f1 --- /dev/null +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js @@ -0,0 +1,141 @@ + +import PropTypes from 'prop-types'; +import React, { Component, Fragment } from 'react'; + +import { EuiButton, EuiButtonIcon } from '../../../button'; +import { EuiFlexGroup, EuiFlexItem } from '../../../flex'; +import { EuiTitle } from '../../../title'; +import { EuiSpacer } from '../../../spacer'; +import { EuiFormRow, EuiSelect, EuiFieldNumber } from '../../../form'; +import { EuiToolTip } from '../../../tool_tip'; + +import { timeUnits } from '../time_units'; + +const LAST = 'last'; +const NEXT = 'next'; + +const timeTenseOptions = [ + { value: LAST, text: 'Last' }, + { value: NEXT, text: 'Next' }, +]; +const timeUnitsOptions = Object.keys(timeUnits).map(key => { + return { value: key, text: `${timeUnits[key]}s` }; +}); + +export class QuickSelect extends Component { + state = { + timeTense: LAST, + timeValue: 15, + timeUnits: 'm', + } + + onTimeTenseChange = (evt) => { + this.setState({ + timeTense: evt.target.value, + }); + } + + onTimeValueChange = (evt) => { + const sanitizedValue = parseInt(evt.target.value, 10); + this.setState({ + timeValue: isNaN(sanitizedValue) ? '' : sanitizedValue, + }); + } + + onTimeUnitsChange = (evt) => { + this.setState({ + timeUnits: evt.target.value, + }); + } + + applyQuickSelect = () => { + const { + timeTense, + timeValue, + timeUnits, + } = this.state; + + if (timeTense === NEXT) { + this.props.applyTime({ + from: 'now', + to: `now+${timeValue}${timeUnits}` + }); + return; + } + + this.props.applyTime({ + from: `now-${timeValue}${timeUnits}`, + to: 'now' + }); + } + + render() { + return ( + + + + Quick select + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Apply + + + + + + ); + } +} + +QuickSelect.propTypes = { + applyTime: PropTypes.func.isRequired, +}; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js similarity index 58% rename from src/components/date_picker/super_date_picker/quick_select_popover.js rename to src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js index ed4926d7dc3..6fa7142dfe0 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import PropTypes from 'prop-types'; import React, { Component, Fragment } from 'react'; -/*import { timeUnits } from '../time_units'; +/* import { timeHistory } from '../../timefilter/time_history'; import { prettyDuration } from '../pretty_duration'; import { RefreshIntervalForm } from './refresh_interval_form';*/ @@ -11,66 +11,48 @@ import { EuiButtonEmpty, EuiButton, EuiButtonIcon, -} from '../../button'; +} from '../../../button'; import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, -} from '../../flex'; +} from '../../../flex'; import { EuiIcon, -} from '../../icon'; +} from '../../../icon'; import { EuiPopover, -} from '../../popover'; +} from '../../../popover'; import { EuiTitle, -} from '../../title'; +} from '../../../title'; import { EuiSpacer, -} from '../../spacer'; - -import { - EuiFormRow, - EuiSelect, - EuiFieldNumber, -} from '../../form'; +} from '../../../spacer'; import { EuiText, -} from '../../text'; +} from '../../../text'; import { EuiHorizontalRule, -} from '../../horizontal_rule'; +} from '../../../horizontal_rule'; import { EuiLink, -} from '../../link'; - -const LAST = 'last'; -const NEXT = 'next'; +} from '../../../link'; -const timeTenseOptions = [ - { value: LAST, text: 'Last' }, - { value: NEXT, text: 'Next' }, -]; -/*const timeUnitsOptions = Object.keys(timeUnits).map(key => { - return { value: key, text: `${timeUnits[key]}s` }; -});*/ +import { QuickSelect } from './quick_select'; export class QuickSelectPopover extends Component { state = { isOpen: false, - timeTense: LAST, - timeValue: 15, - timeUnits: 'm', } closePopover = () => { @@ -83,54 +65,6 @@ export class QuickSelectPopover extends Component { })); } - onTimeTenseChange = (evt) => { - this.setState({ - timeTense: evt.target.value, - }); - } - - onTimeValueChange = (evt) => { - const sanitizedValue = parseInt(evt.target.value, 10); - this.setState({ - timeValue: isNaN(sanitizedValue) ? '' : sanitizedValue, - }); - } - - onTimeUnitsChange = (evt) => { - this.setState({ - timeUnits: evt.target.value, - }); - } - - applyQuickSelect = () => { - const { - timeTense, - timeValue, - timeUnits, - } = this.state; - - if (timeTense === NEXT) { - this.setTime({ - from: 'now', - to: `now+${timeValue}${timeUnits}` - }); - return; - } - - this.setTime({ - from: `now-${timeValue}${timeUnits}`, - to: 'now' - }); - } - - setTime = ({ from, to }) => { - this.props.setTime({ - from: from, - to: to - }); - this.closePopover(); - } - applyTime = ({ from, to }) => { this.props.applyTime({ from: from, @@ -156,57 +90,6 @@ export class QuickSelectPopover extends Component { ); } - renderQuickSelect = () => { - return ( - - Quick select - - - - - - - - - - - - - - - - - - - - - Apply - - - - - - ); - } - renderCommonlyUsed = () => { const commonlyUsed = chrome.getUiSettingsClient().get('timepicker:quickRanges'); const sections = _.groupBy(commonlyUsed, 'section'); @@ -303,7 +186,10 @@ export class QuickSelectPopover extends Component { ownFocus >
- Quick select + +
); @@ -312,7 +198,6 @@ export class QuickSelectPopover extends Component { QuickSelectPopover.propTypes = { applyTime: PropTypes.func.isRequired, - setTime: PropTypes.func.isRequired, setRefresh: PropTypes.func.isRequired, isPaused: PropTypes.bool.isRequired, refreshInterval: PropTypes.number, diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index 6dd22207426..ed261cf4cc5 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -21,9 +21,9 @@ import moment from 'moment'; import PropTypes from 'prop-types'; import React, { Component, Fragment } from 'react'; -/*import dateMath from '@kbn/datemath';*/ +import dateMath from '@elastic/datemath'; -import { QuickSelectPopover } from './quick_select_popover'; +import { QuickSelectPopover } from './quick_select_popover/quick_select_popover'; //import { TimeInput } from './time_input'; import { @@ -66,27 +66,22 @@ export class EuiSuperDatePicker extends Component { from: nextProps.from, to: nextProps.to, isInvalid: false, + errorMessage: undefined, hasChanged: false, isEditMode: false, }; } setTime = ({ from, to }) => { - // TODO validation once dateMath is an npm artifact - /*const fromMoment = dateMath.parse(from); + const fromMoment = dateMath.parse(from); const toMoment = dateMath.parse(to, { roundUp: true }); const isInvalid = fromMoment.isAfter(toMoment); - if (isInvalid) { - this.lastToast = toastNotifications.addDanger({ - title: `Invalid time range`, - text: `From must occur before To`, - }); - }*/ this.setState({ from, to, isInvalid: false, + errorMessage: isInvalid ? 'Invalid time range, "from" must occur before "to"' : undefined, hasChanged: true, }); } @@ -146,8 +141,7 @@ export class EuiSuperDatePicker extends Component { render() { const quickSelect = ( Date: Tue, 4 Dec 2018 12:39:06 -0700 Subject: [PATCH 03/37] commonly used ranges --- .../quick_select_popover/commonly_used.js | 53 +++++++++++++++++++ .../quick_select_popover.js | 50 ++--------------- .../date_picker/super_date_picker/types.js | 7 +++ 3 files changed, 65 insertions(+), 45 deletions(-) create mode 100644 src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js create mode 100644 src/components/date_picker/super_date_picker/types.js diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js b/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js new file mode 100644 index 00000000000..b5c0f93364c --- /dev/null +++ b/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js @@ -0,0 +1,53 @@ + +import PropTypes from 'prop-types'; +import React, { Fragment } from 'react'; +import { commonlyUsedRange } from '../types'; + +import { EuiFlexGrid, EuiFlexItem } from '../../../flex'; +import { EuiTitle } from '../../../title'; +import { EuiSpacer } from '../../../spacer'; +import { EuiLink } from '../../../link'; +import { EuiText } from '../../../text'; + +export function CommonlyUsed({ commonlyUsedRanges, applyTime }) { + const links = commonlyUsedRanges.map(({ from, to, label }) => { + const applyCommonlyUsedTime = () => { + applyTime({ from, to }); + }; + return ( + + {label} + + ); + }); + + return ( + + Commonly used + + + + {links} + + + + ); +} + +CommonlyUsed.propTypes = { + commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRange), + applyTime: PropTypes.func.isRequired, +}; + +CommonlyUsed.defaultProps = { + commonlyUsedRanges: [ + { from: 'now/d', to: 'now/d', label: 'Today' }, + { from: 'now-1d/d', to: 'now-1d/d', label: 'Yesterday' }, + { from: 'now/w', to: 'now/w', label: 'This week' }, + { from: 'now/w', to: 'now', label: 'Week to date' }, + { from: 'now/M', to: 'now/M', label: 'This month' }, + { from: 'now/M', to: 'now', label: 'Month to date' }, + { from: 'now/y', to: 'now/y', label: 'This year' }, + { from: 'now/y', to: 'now', label: 'Year to date' }, + ] +}; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js index 6fa7142dfe0..8e37bf3ea39 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js @@ -48,6 +48,7 @@ import { } from '../../../link'; import { QuickSelect } from './quick_select'; +import { CommonlyUsed } from './commonly_used'; export class QuickSelectPopover extends Component { @@ -90,51 +91,6 @@ export class QuickSelectPopover extends Component { ); } - renderCommonlyUsed = () => { - const commonlyUsed = chrome.getUiSettingsClient().get('timepicker:quickRanges'); - const sections = _.groupBy(commonlyUsed, 'section'); - - const renderSectionItems = (section) => { - return section.map(({ from, to, display }) => { - const applyTime = () => { - this.applyTime({ from, to }); - }; - return ( - - {display} - - ); - }); - }; - - return ( - - Commonly used - - - {Object.keys(sections).map((key, index) => { - const isLastSection = Object.keys(sections).length - 1 === index; - const sectionSpacer = isLastSection - ? undefined - : (); - return ( - - - {renderSectionItems(sections[key])} - - {sectionSpacer} - - ); - })} - - - ); - } - renderRecentlyUsed = () => { const links = timeHistory.get().map(({ from, to }) => { const applyTime = () => { @@ -190,6 +146,10 @@ export class QuickSelectPopover extends Component { applyTime={this.applyTime} /> + + ); diff --git a/src/components/date_picker/super_date_picker/types.js b/src/components/date_picker/super_date_picker/types.js new file mode 100644 index 00000000000..22a38b4892f --- /dev/null +++ b/src/components/date_picker/super_date_picker/types.js @@ -0,0 +1,7 @@ +import PropTypes from 'prop-types'; + +export const commonlyUsedRange = PropTypes.shape({ + from: PropTypes.string.isRequired, + to: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, +}); From d42a1b17dc9e51fbabc349abf8bb04b9409078d4 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 4 Dec 2018 14:54:48 -0700 Subject: [PATCH 04/37] recently used --- package.json | 1 + .../views/date_picker/super_date_picker.js | 16 ++++-- .../super_date_picker/pretty_duration.js | 53 +++++++++++++++++++ .../super_date_picker/pretty_duration.test.js | 44 +++++++++++++++ .../quick_select_popover/commonly_used.js | 23 ++------ .../quick_select_popover.js | 45 ++++++---------- .../quick_select_popover/recently_used.js | 53 +++++++++++++++++++ .../super_date_picker/super_date_picker.js | 26 +++++++-- .../date_picker/super_date_picker/types.js | 7 ++- yarn.lock | 9 +++- 10 files changed, 222 insertions(+), 55 deletions(-) create mode 100644 src/components/date_picker/super_date_picker/pretty_duration.js create mode 100644 src/components/date_picker/super_date_picker/pretty_duration.test.js create mode 100644 src/components/date_picker/super_date_picker/quick_select_popover/recently_used.js diff --git a/package.json b/package.json index c6f62723c1c..f2b636f72f3 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,7 @@ "markdown-it": "8.4.1", "mocha": "^5.0.4", "moment": "^2.20.1", + "moment-timezone": "^0.5.14", "node-sass": "^4.9.3", "nodegit": "^0.23.0", "npm-run": "^4.1.2", diff --git a/src-docs/src/views/date_picker/super_date_picker.js b/src-docs/src/views/date_picker/super_date_picker.js index 5352288f53f..4c7daae0cf5 100644 --- a/src-docs/src/views/date_picker/super_date_picker.js +++ b/src-docs/src/views/date_picker/super_date_picker.js @@ -7,10 +7,20 @@ import { export default class extends Component { - state = {} + state = { + recentlyUsedRanges: [] + } onTimeChange = ({ from, to }) => { - this.setState({ from, to }); + this.setState((prevState) => { + const recentlyUsedRanges = [...prevState.recentlyUsedRanges]; + recentlyUsedRanges.push({ from, to }); + return { + from, + to, + recentlyUsedRanges: recentlyUsedRanges.length > 10 ? recentlyUsedRanges.slice(0, 9) : recentlyUsedRanges, + }; + }); } onRefreshChange = ({ isPaused, refreshInterval }) => { @@ -18,7 +28,6 @@ export default class extends Component { } render() { - return ( ); } diff --git a/src/components/date_picker/super_date_picker/pretty_duration.js b/src/components/date_picker/super_date_picker/pretty_duration.js new file mode 100644 index 00000000000..6db1be99af5 --- /dev/null +++ b/src/components/date_picker/super_date_picker/pretty_duration.js @@ -0,0 +1,53 @@ + +import dateMath from '@elastic/datemath'; +import moment from 'moment'; +import { timeUnits } from './time_units'; + +const ISO_FORMAT = 'YYYY-MM-DDTHH:mm:ss.sssZ'; + +function cantLookup(timeFrom, timeTo, dateFormat) { + const displayFrom = formatTimeString(timeFrom, dateFormat); + const displayTo = formatTimeString(timeTo, dateFormat, true); + return `${displayFrom} to ${displayTo}`; +} + +function getRangeKey(from, to) { + return `${from} to ${to}`; +} + +export function formatTimeString(timeString, dateFormat, roundUp = false) { + const timeAsMoment = moment(timeString, ISO_FORMAT, true); + if (timeAsMoment.isValid()) { + console.log('ISO_FORMAT', timeAsMoment.format(ISO_FORMAT)); + return timeAsMoment.format(dateFormat); + } + + if (timeString === 'now') { + return 'now'; + } + + const tryParse = dateMath.parse(timeString, { roundUp: roundUp }); + return moment.isMoment(tryParse) ? '~ ' + tryParse.fromNow() : timeString; +} + +export function prettyDuration(timeFrom, timeTo, quickRanges = [], dateFormat) { + const matchingQuickRange = quickRanges.find(({ from: quickFrom, to: quickTo }) => { + return timeFrom === quickFrom && timeTo === quickTo; + }); + if (matchingQuickRange) { + return matchingQuickRange.label; + } + + const fromParts = timeFrom.split('-'); + if (timeTo === 'now' && fromParts[0] === 'now' && fromParts[1]) { + const rounded = fromParts[1].split('/'); + let text = `Last ${rounded[0]}`; + if (rounded[1]) { + const timeUnit = timeUnits[rounded[1]] ? timeUnits[rounded[1]] : rounded[1]; + text = `${text} rounded to the ${timeUnit}`; + } + return text; + } + + return cantLookup(timeFrom, timeTo, dateFormat); +} diff --git a/src/components/date_picker/super_date_picker/pretty_duration.test.js b/src/components/date_picker/super_date_picker/pretty_duration.test.js new file mode 100644 index 00000000000..9e4ea99c506 --- /dev/null +++ b/src/components/date_picker/super_date_picker/pretty_duration.test.js @@ -0,0 +1,44 @@ + +import moment from 'moment-timezone'; +import { prettyDuration } from './pretty_duration'; + +moment.tz.setDefault('UTC'); +const dateFormat = 'MMMM Do YYYY, HH:mm:ss.SSS'; +const quickRanges = [ + { + from: 'now-15m', + to: 'now', + label: 'quick range 15 minutes custom display', + } +]; + +test('quick range', () => { + const timeFrom = 'now-15m'; + const timeTo = 'now'; + expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('quick range 15 minutes custom display'); +}); + +test('last', () => { + const timeFrom = 'now-1M'; + const timeTo = 'now'; + expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('Last 1M'); +}); + +test('last that is rounded', () => { + const timeFrom = 'now-1M/w'; + const timeTo = 'now'; + expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('Last 1M rounded to the week'); +}); + +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'); +}); + +// TODO figure out timezone to get this working +//test('absolute dates', () => { +// const timeFrom = '2018-01-17T18:57:57.149Z'; +// const timeTo = '2018-01-17T20:00:00.000Z'; +// expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('January 17th 2018, 18:57:57.149 to January 17th 2018, 20:00:00.000'); +//}); diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js b/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js index b5c0f93364c..724f52c8c1e 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import React, { Fragment } from 'react'; -import { commonlyUsedRange } from '../types'; +import { commonlyUsedRangeShape } from '../types'; import { EuiFlexGrid, EuiFlexItem } from '../../../flex'; import { EuiTitle } from '../../../title'; @@ -9,14 +9,14 @@ import { EuiSpacer } from '../../../spacer'; import { EuiLink } from '../../../link'; import { EuiText } from '../../../text'; -export function CommonlyUsed({ commonlyUsedRanges, applyTime }) { +export function CommonlyUsed({ applyTime, commonlyUsedRanges }) { const links = commonlyUsedRanges.map(({ from, to, label }) => { - const applyCommonlyUsedTime = () => { + const applyCommonlyUsed = () => { applyTime({ from, to }); }; return ( - {label} + {label} ); }); @@ -35,19 +35,6 @@ export function CommonlyUsed({ commonlyUsedRanges, applyTime }) { } CommonlyUsed.propTypes = { - commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRange), applyTime: PropTypes.func.isRequired, -}; - -CommonlyUsed.defaultProps = { - commonlyUsedRanges: [ - { from: 'now/d', to: 'now/d', label: 'Today' }, - { from: 'now-1d/d', to: 'now-1d/d', label: 'Yesterday' }, - { from: 'now/w', to: 'now/w', label: 'This week' }, - { from: 'now/w', to: 'now', label: 'Week to date' }, - { from: 'now/M', to: 'now/M', label: 'This month' }, - { from: 'now/M', to: 'now', label: 'Month to date' }, - { from: 'now/y', to: 'now/y', label: 'This year' }, - { from: 'now/y', to: 'now', label: 'Year to date' }, - ] + commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRangeShape).isRequired, }; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js index 8e37bf3ea39..0ac12fd2ade 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js @@ -1,10 +1,10 @@ import _ from 'lodash'; import PropTypes from 'prop-types'; import React, { Component, Fragment } from 'react'; +import { commonlyUsedRangeShape, recentlyUsedRangeShape } from '../types'; /* import { timeHistory } from '../../timefilter/time_history'; -import { prettyDuration } from '../pretty_duration'; import { RefreshIntervalForm } from './refresh_interval_form';*/ import { @@ -49,6 +49,7 @@ import { import { QuickSelect } from './quick_select'; import { CommonlyUsed } from './commonly_used'; +import { RecentlyUsed } from './recently_used'; export class QuickSelectPopover extends Component { @@ -68,8 +69,8 @@ export class QuickSelectPopover extends Component { applyTime = ({ from, to }) => { this.props.applyTime({ - from: from, - to: to + from, + to, }); this.closePopover(); } @@ -91,32 +92,6 @@ export class QuickSelectPopover extends Component { ); } - renderRecentlyUsed = () => { - const links = timeHistory.get().map(({ from, to }) => { - const applyTime = () => { - this.applyTime({ from, to }); - }; - const display = prettyDuration(from, to, (...args) => chrome.getUiSettingsClient().get(...args)); - return ( - - {display} - - ); - }); - - return ( - - Recently used date ranges - - - - {links} - - - - ); - } - render() { const quickSelectButton = ( + + @@ -161,4 +145,7 @@ QuickSelectPopover.propTypes = { setRefresh: PropTypes.func.isRequired, isPaused: PropTypes.bool.isRequired, refreshInterval: PropTypes.number, + commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRangeShape).isRequired, + dateFormat: PropTypes.string.isRequired, + recentlyUsedRanges: PropTypes.arrayOf(recentlyUsedRangeShape).isRequired, }; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/recently_used.js b/src/components/date_picker/super_date_picker/quick_select_popover/recently_used.js new file mode 100644 index 00000000000..cb5837b1a4b --- /dev/null +++ b/src/components/date_picker/super_date_picker/quick_select_popover/recently_used.js @@ -0,0 +1,53 @@ + +import PropTypes from 'prop-types'; +import React, { Fragment } from 'react'; +import { commonlyUsedRangeShape, recentlyUsedRangeShape } from '../types'; +import { prettyDuration } from '../pretty_duration'; + +import { EuiFlexGroup, EuiFlexItem } from '../../../flex'; +import { EuiTitle } from '../../../title'; +import { EuiSpacer } from '../../../spacer'; +import { EuiLink } from '../../../link'; +import { EuiText } from '../../../text'; + +export function RecentlyUsed({ applyTime, commonlyUsedRanges, dateFormat, recentlyUsedRanges }) { + if (recentlyUsedRanges.length === 0) { + return null; + } + + const links = recentlyUsedRanges.map(({ from, to }) => { + const applyRecentlyUsed = () => { + applyTime({ from, to }); + }; + return ( + + + {prettyDuration(from, to, commonlyUsedRanges, dateFormat)} + + + ); + }); + + return ( + + Recently used date ranges + + + + {links} + + + + ); +} + +RecentlyUsed.propTypes = { + applyTime: PropTypes.func.isRequired, + commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRangeShape).isRequired, + dateFormat: PropTypes.string.isRequired, + recentlyUsedRanges: PropTypes.arrayOf(recentlyUsedRangeShape), +}; + +RecentlyUsed.defaultProps = { + recentlyUsedRanges: [] +}; diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index ed261cf4cc5..a5ed2f53501 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -20,6 +20,7 @@ import moment from 'moment'; import PropTypes from 'prop-types'; import React, { Component, Fragment } from 'react'; +import { commonlyUsedRangeShape, recentlyUsedRangeShape } from './types'; import dateMath from '@elastic/datemath'; @@ -107,11 +108,11 @@ export class EuiSuperDatePicker extends Component { }*/ applyTime = () => { - this.props.onTimeChange(this.state.from, this.state.to); + this.props.onTimeChange({ from: this.state.from, to: this.state.to }); } applyQuickTime = ({ from, to }) => { - this.props.onTimeChange(from, to); + this.props.onTimeChange({ from, to }); } renderDateRange = () => { @@ -144,9 +145,12 @@ export class EuiSuperDatePicker extends Component { applyTime={this.applyQuickTime} stepForward={this.stepForward} stepBackward={this.stepBackward} - setRefresh={this.props.setRefresh} + setRefresh={this.props.onRefreshChange} isPaused={this.props.isPaused} refreshInterval={this.props.refreshInterval} + commonlyUsedRanges={this.props.commonlyUsedRanges} + dateFormat={this.props.dateFormat} + recentlyUsedRanges={this.props.recentlyUsedRanges} /> ); return ( @@ -177,6 +181,10 @@ EuiSuperDatePicker.propTypes = { isPaused: PropTypes.bool, refreshInterval: PropTypes.number, onRefreshChange: PropTypes.func.isRequired, + + commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRangeShape), + dateFormat: PropTypes.string, + recentlyUsedRanges: PropTypes.arrayOf(recentlyUsedRangeShape), }; EuiSuperDatePicker.defaultProps = { @@ -184,5 +192,17 @@ EuiSuperDatePicker.defaultProps = { to: 'now', isPaused: true, refreshInterval: 0, + commonlyUsedRanges: [ + { from: 'now/d', to: 'now/d', label: 'Today' }, + { from: 'now-1d/d', to: 'now-1d/d', label: 'Yesterday' }, + { from: 'now/w', to: 'now/w', label: 'This week' }, + { from: 'now/w', to: 'now', label: 'Week to date' }, + { from: 'now/M', to: 'now/M', label: 'This month' }, + { from: 'now/M', to: 'now', label: 'Month to date' }, + { from: 'now/y', to: 'now/y', label: 'This year' }, + { from: 'now/y', to: 'now', label: 'Year to date' }, + ], + dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', + recentlyUsedRanges: [], }; diff --git a/src/components/date_picker/super_date_picker/types.js b/src/components/date_picker/super_date_picker/types.js index 22a38b4892f..78e4c817a16 100644 --- a/src/components/date_picker/super_date_picker/types.js +++ b/src/components/date_picker/super_date_picker/types.js @@ -1,7 +1,12 @@ import PropTypes from 'prop-types'; -export const commonlyUsedRange = PropTypes.shape({ +export const commonlyUsedRangeShape = PropTypes.shape({ from: PropTypes.string.isRequired, to: PropTypes.string.isRequired, label: PropTypes.string.isRequired, }); + +export const recentlyUsedRangeShape = PropTypes.shape({ + from: PropTypes.string.isRequired, + to: PropTypes.string.isRequired, +}); diff --git a/yarn.lock b/yarn.lock index 811e03dc147..fbcaf50f73f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8801,12 +8801,19 @@ mocha@^5.0.0, mocha@^5.0.4: mkdirp "0.5.1" supports-color "4.4.0" +moment-timezone@^0.5.14: + version "0.5.23" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.23.tgz#7cbb00db2c14c71b19303cb47b0fb0a6d8651463" + integrity sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w== + dependencies: + moment ">= 2.9.0" + moment@2.x.x: version "2.21.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a" integrity sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ== -moment@^2.13.0: +"moment@>= 2.9.0", moment@^2.13.0: version "2.22.2" resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= From 325168f44e552ab0744c589db91fd089fa0dbf1e Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 5 Dec 2018 06:55:41 -0700 Subject: [PATCH 05/37] cleanup quick_select_popover --- .../views/date_picker/super_date_picker.js | 5 +- .../super_date_picker/pretty_duration.js | 10 ++-- .../quick_select_popover/commonly_used.js | 2 + .../quick_select_popover/quick_select.js | 2 + .../quick_select_popover.js | 58 +------------------ .../quick_select_popover/recently_used.js | 2 + 6 files changed, 18 insertions(+), 61 deletions(-) diff --git a/src-docs/src/views/date_picker/super_date_picker.js b/src-docs/src/views/date_picker/super_date_picker.js index 4c7daae0cf5..6bad16ceea6 100644 --- a/src-docs/src/views/date_picker/super_date_picker.js +++ b/src-docs/src/views/date_picker/super_date_picker.js @@ -13,7 +13,10 @@ export default class extends Component { onTimeChange = ({ from, to }) => { this.setState((prevState) => { - const recentlyUsedRanges = [...prevState.recentlyUsedRanges]; + const recentlyUsedRanges = prevState.recentlyUsedRanges.filter(recentlyUsedRange => { + const isDuplicate = recentlyUsedRange.from === from && recentlyUsedRange.to === to; + return !isDuplicate; + }); recentlyUsedRanges.push({ from, to }); return { from, diff --git a/src/components/date_picker/super_date_picker/pretty_duration.js b/src/components/date_picker/super_date_picker/pretty_duration.js index 6db1be99af5..836b1d03af7 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.js +++ b/src/components/date_picker/super_date_picker/pretty_duration.js @@ -11,10 +11,6 @@ function cantLookup(timeFrom, timeTo, dateFormat) { return `${displayFrom} to ${displayTo}`; } -function getRangeKey(from, to) { - return `${from} to ${to}`; -} - export function formatTimeString(timeString, dateFormat, roundUp = false) { const timeAsMoment = moment(timeString, ISO_FORMAT, true); if (timeAsMoment.isValid()) { @@ -27,7 +23,11 @@ export function formatTimeString(timeString, dateFormat, roundUp = false) { } const tryParse = dateMath.parse(timeString, { roundUp: roundUp }); - return moment.isMoment(tryParse) ? '~ ' + tryParse.fromNow() : timeString; + if (moment.isMoment(tryParse)) { + return `~ ${tryParse.fromNow()}`; + } + + return timeString; } export function prettyDuration(timeFrom, timeTo, quickRanges = [], dateFormat) { diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js b/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js index 724f52c8c1e..64ae8961180 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js @@ -8,6 +8,7 @@ import { EuiTitle } from '../../../title'; import { EuiSpacer } from '../../../spacer'; import { EuiLink } from '../../../link'; import { EuiText } from '../../../text'; +import { EuiHorizontalRule } from '../../../horizontal_rule'; export function CommonlyUsed({ applyTime, commonlyUsedRanges }) { const links = commonlyUsedRanges.map(({ from, to, label }) => { @@ -30,6 +31,7 @@ export function CommonlyUsed({ applyTime, commonlyUsedRanges }) { {links}
+ ); } diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js index 4f80f2ca6f1..878ca227d67 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js @@ -8,6 +8,7 @@ import { EuiTitle } from '../../../title'; import { EuiSpacer } from '../../../spacer'; import { EuiFormRow, EuiSelect, EuiFieldNumber } from '../../../form'; import { EuiToolTip } from '../../../tool_tip'; +import { EuiHorizontalRule } from '../../../horizontal_rule'; import { timeUnits } from '../time_units'; @@ -131,6 +132,7 @@ export class QuickSelect extends Component { + ); } diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js index 0ac12fd2ade..d22071ed59b 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js @@ -1,24 +1,12 @@ -import _ from 'lodash'; + import PropTypes from 'prop-types'; -import React, { Component, Fragment } from 'react'; +import React, { Component } from 'react'; import { commonlyUsedRangeShape, recentlyUsedRangeShape } from '../types'; -/* -import { timeHistory } from '../../timefilter/time_history'; -import { RefreshIntervalForm } from './refresh_interval_form';*/ - import { EuiButtonEmpty, - EuiButton, - EuiButtonIcon, } from '../../../button'; -import { - EuiFlexGrid, - EuiFlexGroup, - EuiFlexItem, -} from '../../../flex'; - import { EuiIcon, } from '../../../icon'; @@ -27,26 +15,6 @@ import { EuiPopover, } from '../../../popover'; -import { - EuiTitle, -} from '../../../title'; - -import { - EuiSpacer, -} from '../../../spacer'; - -import { - EuiText, -} from '../../../text'; - -import { - EuiHorizontalRule, -} from '../../../horizontal_rule'; - -import { - EuiLink, -} from '../../../link'; - import { QuickSelect } from './quick_select'; import { CommonlyUsed } from './commonly_used'; import { RecentlyUsed } from './recently_used'; @@ -75,23 +43,6 @@ export class QuickSelectPopover extends Component { this.closePopover(); } - renderTimeNavigation = () => { - return ( - - - - - ); - } - render() { const quickSelectButton = ( - + ); @@ -121,19 +72,16 @@ export class QuickSelectPopover extends Component { applyTime={this.applyTime} commonlyUsedRanges={this.props.commonlyUsedRanges} /> - - - ); diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/recently_used.js b/src/components/date_picker/super_date_picker/quick_select_popover/recently_used.js index cb5837b1a4b..419d86eb24a 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/recently_used.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/recently_used.js @@ -9,6 +9,7 @@ import { EuiTitle } from '../../../title'; import { EuiSpacer } from '../../../spacer'; import { EuiLink } from '../../../link'; import { EuiText } from '../../../text'; +import { EuiHorizontalRule } from '../../../horizontal_rule'; export function RecentlyUsed({ applyTime, commonlyUsedRanges, dateFormat, recentlyUsedRanges }) { if (recentlyUsedRanges.length === 0) { @@ -37,6 +38,7 @@ export function RecentlyUsed({ applyTime, commonlyUsedRanges, dateFormat, recent {links} + ); } From 0aa79ef62d938a713fd660542e636934b047d7bc Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 5 Dec 2018 10:43:25 -0700 Subject: [PATCH 06/37] refresh interval --- .../views/date_picker/super_date_picker.js | 7 +- .../quick_select_popover.js | 10 +- .../quick_select_popover/refresh_interval.js | 137 ++++++++++++++++++ .../super_date_picker/super_date_picker.js | 2 +- 4 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js diff --git a/src-docs/src/views/date_picker/super_date_picker.js b/src-docs/src/views/date_picker/super_date_picker.js index 6bad16ceea6..6bf6ad66c7c 100644 --- a/src-docs/src/views/date_picker/super_date_picker.js +++ b/src-docs/src/views/date_picker/super_date_picker.js @@ -27,7 +27,12 @@ export default class extends Component { } onRefreshChange = ({ isPaused, refreshInterval }) => { - this.setState({ isPaused, refreshInterval }); + this.setState((prevState) => { + return { + isPaused: isPaused == null ? prevState.isPaused : isPaused, + refreshInterval: refreshInterval == null ? prevState.refreshInterval : refreshInterval, + }; + }); } render() { diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js index d22071ed59b..65c67586286 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js @@ -18,6 +18,7 @@ import { import { QuickSelect } from './quick_select'; import { CommonlyUsed } from './commonly_used'; import { RecentlyUsed } from './recently_used'; +import { RefreshInterval } from './refresh_interval'; export class QuickSelectPopover extends Component { @@ -82,6 +83,11 @@ export class QuickSelectPopover extends Component { dateFormat={this.props.dateFormat} recentlyUsedRanges={this.props.recentlyUsedRanges} /> + ); @@ -90,9 +96,9 @@ export class QuickSelectPopover extends Component { QuickSelectPopover.propTypes = { applyTime: PropTypes.func.isRequired, - setRefresh: PropTypes.func.isRequired, + applyRefreshInterval: PropTypes.func.isRequired, isPaused: PropTypes.bool.isRequired, - refreshInterval: PropTypes.number, + refreshInterval: PropTypes.number.isRequired, commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRangeShape).isRequired, dateFormat: PropTypes.string.isRequired, recentlyUsedRanges: PropTypes.arrayOf(recentlyUsedRangeShape).isRequired, diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js b/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js new file mode 100644 index 00000000000..32bf457a9ae --- /dev/null +++ b/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js @@ -0,0 +1,137 @@ +import PropTypes from 'prop-types'; +import React, { Component, Fragment } from 'react'; +import { timeUnits } from '../time_units'; + +import { EuiFlexGroup, EuiFlexItem } from '../../../flex'; +import { EuiTitle } from '../../../title'; +import { EuiSpacer } from '../../../spacer'; +import { EuiFormRow, EuiSelect, EuiFieldNumber } from '../../../form'; +import { EuiButton } from '../../../button'; + +const refreshUnitsOptions = Object.keys(timeUnits) + .filter(timeUnit => { + return timeUnit === 'h' || timeUnit === 'm'; + }) + .map(timeUnit => { + return { value: timeUnit, text: `${timeUnits[timeUnit]}s` }; + }); + +const MILLISECONDS_IN_MINUTE = 1000 * 60; +const MILLISECONDS_IN_HOUR = MILLISECONDS_IN_MINUTE * 60; + +function convertMilliseconds(milliseconds) { + if (milliseconds > MILLISECONDS_IN_HOUR) { + return { + units: 'h', + value: milliseconds / MILLISECONDS_IN_HOUR + }; + } + + return { + units: 'm', + value: milliseconds / MILLISECONDS_IN_MINUTE + }; +} + +export class RefreshInterval extends Component { + constructor(props) { + super(props); + + const { value, units } = convertMilliseconds(this.props.refreshInterval); + this.state = { + value, + units, + }; + } + + static getDerivedStateFromProps = (nextProps) => { + const { value, units } = convertMilliseconds(nextProps.refreshInterval); + return { + value, + units, + }; + } + + onValueChange = (evt) => { + const sanitizedValue = parseInt(evt.target.value, 10); + this.setState({ + value: isNaN(sanitizedValue) ? '' : sanitizedValue, + }, this.applyRefreshInterval); + }; + + onUnitsChange = (evt) => { + this.setState({ + units: evt.target.value, + }, this.applyRefreshInterval); + } + + applyRefreshInterval = () => { + if (this.state.value === '') { + return; + } + + const valueInMilliSeconds = this.state.units === 'h' + ? this.state.value * MILLISECONDS_IN_HOUR + : this.state.value * MILLISECONDS_IN_MINUTE; + + this.props.applyRefreshInterval({ + refreshInterval: valueInMilliSeconds, + isPaused: valueInMilliSeconds <= 0 ? true : this.props.isPaused, + }); + } + + toogleRefresh = () => { + this.props.applyRefreshInterval({ + isPaused: !this.props.isPaused + }); + } + + render() { + return ( + + Refresh every + + + + + + + + + + + + + + + + {this.props.isPaused ? 'Start' : 'Stop'} + + + + + + ); + } +} + +RefreshInterval.propTypes = { + applyRefreshInterval: PropTypes.func.isRequired, + isPaused: PropTypes.bool.isRequired, + refreshInterval: PropTypes.number.isRequired, +}; diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index a5ed2f53501..25a1e5db608 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -145,7 +145,7 @@ export class EuiSuperDatePicker extends Component { applyTime={this.applyQuickTime} stepForward={this.stepForward} stepBackward={this.stepBackward} - setRefresh={this.props.onRefreshChange} + applyRefreshInterval={this.props.onRefreshChange} isPaused={this.props.isPaused} refreshInterval={this.props.refreshInterval} commonlyUsedRanges={this.props.commonlyUsedRanges} From f0c95440c2cb9f7db0330e3b8ec3fd85f2938355 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 5 Dec 2018 12:29:28 -0700 Subject: [PATCH 07/37] show date range --- .../super_date_picker/date_button.js | 182 +++++------------- .../super_date_picker/super_date_picker.js | 90 +++++++-- 2 files changed, 122 insertions(+), 150 deletions(-) diff --git a/src/components/date_picker/super_date_picker/date_button.js b/src/components/date_picker/super_date_picker/date_button.js index 0c8d7aded15..a4237972b0a 100644 --- a/src/components/date_picker/super_date_picker/date_button.js +++ b/src/components/date_picker/super_date_picker/date_button.js @@ -1,127 +1,77 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import chrome from 'ui/chrome'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import { AbsoluteForm } from './absolute_form'; -import { RelativeForm } from './relative_form'; - -import { - EuiPopover, - EuiTabbedContent, - EuiText, -} from '@elastic/eui'; +import classNames from 'classnames'; -import { formatTimeString } from '../pretty_duration'; -import { - getTimeMode, - TIME_MODES, - toAbsoluteString, - toRelativeString, -} from '../lib/time_modes'; +import { EuiPopover } from '../../popover'; export class DateButton extends Component { + static propTypes = { + position: PropTypes.oneOf(['start', 'end']), + isInvalid: PropTypes.bool, + needsUpdating: PropTypes.bool, + buttonOnly: PropTypes.bool, + value: PropTypes.string.isRequired, + } - constructor(props) { - super(props); + state = { + isOpen: false, + } - this.state = { + togglePopover = () => { + this.setState((prevState) => { + return { isOpen: !prevState.isOpen }; + }); + } + + closePopover = () => { + this.setState({ isOpen: false, - }; + }); } - onTabClick = (selectedTab) => { + render() { const { + position, + isInvalid, + needsUpdating, value, - roundUp + buttonProps, + buttonOnly, + ...rest } = this.props; - switch(selectedTab.id) { - case TIME_MODES.ABSOLUTE: - this.props.onChange(toAbsoluteString(value, roundUp)); - break; - case TIME_MODES.RELATIVE: - this.props.onChange(toRelativeString(value)); - break; - case TIME_MODES.NOW: - this.props.onChange('now'); - break; - } - }; - - closePopover = () => { - this.setState({ isOpen: false }); - } - - togglePopover = () => { - this.setState((prevState) => ({ - isOpen: !prevState.isOpen, - })); - } - - renderTabs = () => { - return [ - { - id: TIME_MODES.ABSOLUTE, - name: 'Absolute', - content: ( - - ), - }, + const classes = classNames([ + 'euiSuperDatePicker__dateButton', + `euiSuperDatePicker__dateButton--${position}`, { - id: TIME_MODES.RELATIVE, - name: 'Relative', - content: ( - - ), - }, - { - id: TIME_MODES.NOW, - name: 'Now', - content: ( - - ), + 'euiSuperDatePicker__dateButton-isSelected': this.state.isOpen, + 'euiSuperDatePicker__dateButton-isInvalid': isInvalid, + 'euiSuperDatePicker__dateButton-needsUpdating': needsUpdating } - ]; - } + ]); - render() { - const input = ( + let title = value; + if (isInvalid) { + title = `Invalid date: ${title}`; + } else if (needsUpdating) { + title = `Update needed: ${title}`; + } + + const button = ( ); + if (buttonOnly) { + return button; + } + return ( - - - - +
Date selection popover
); } } - -DateButton.propTypes = { - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - roundUp: PropTypes.bool, -}; - -DateButton.defaultProps = { - roundUp: false, -}; diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index 25a1e5db608..1765c637f2b 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -21,28 +21,18 @@ import moment from 'moment'; import PropTypes from 'prop-types'; import React, { Component, Fragment } from 'react'; import { commonlyUsedRangeShape, recentlyUsedRangeShape } from './types'; +import { prettyDuration } from './pretty_duration'; import dateMath from '@elastic/datemath'; import { QuickSelectPopover } from './quick_select_popover/quick_select_popover'; -//import { TimeInput } from './time_input'; +import { DateButton } from './date_button'; -import { - EuiFormControlLayout, -} from '../../form'; - -import { - EuiText, -} from '../../text'; - -import { - EuiButton, -} from '../../button'; - -import { - EuiFlexGroup, - EuiFlexItem, -} from '../../flex'; +import { EuiDatePickerRange } from '../date_picker_range'; +import { EuiFormControlLayout } from '../../form'; +import { EuiText } from '../../text'; +import { EuiButton, EuiButtonEmpty } from '../../button'; +import { EuiFlexGroup, EuiFlexItem } from '../../flex'; /*import { prettyDuration } from '../pretty_duration'; import { timeNavigation } from '../time_navigation'; @@ -115,8 +105,66 @@ export class EuiSuperDatePicker extends Component { this.props.onTimeChange({ from, to }); } - renderDateRange = () => { - return; + toggleEditMode = () => { + this.setState({ isEditMode: true }); + } + + renderDatePickerRange = () => { + const { + from, + to, + hasChanged, + isInvalid, + } = this.state; + + let prettyDurationDateRange; + if (!this.state.isEditMode) { + prettyDurationDateRange = ( + + + + Show dates + + + ); + } + + // Why isn't prettyDurationDateRange just returned and instead passed as a child to EuiDatePickerRange? + // Need classes provided by EuiDatePickerRange for consistent view and + // EuiDatePickerRange does not render startDateControl of endDateControl when children are provided. + return ( + + } + endDateControl={ + + } + > + {prettyDurationDateRange} + + ); } renderUpdateButton = () => { @@ -158,10 +206,10 @@ export class EuiSuperDatePicker extends Component { - {this.renderDateRange()} + {this.renderDatePickerRange()} From e823450f787278962a7b827e43e4c272bcf1bea9 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 5 Dec 2018 15:30:07 -0700 Subject: [PATCH 08/37] relative date parsing --- .../{ => date_popover}/date_button.js | 14 ++- .../date_popover/date_input.js | 75 +++++++++++ .../date_popover/date_modes.js | 30 +++++ .../date_popover/relative_options.js | 18 +++ .../date_popover/relative_utils.js | 61 +++++++++ .../date_popover/relative_utils.test.js | 119 ++++++++++++++++++ .../super_date_picker/super_date_picker.js | 9 +- 7 files changed, 321 insertions(+), 5 deletions(-) rename src/components/date_picker/super_date_picker/{ => date_popover}/date_button.js (82%) create mode 100644 src/components/date_picker/super_date_picker/date_popover/date_input.js create mode 100644 src/components/date_picker/super_date_picker/date_popover/date_modes.js create mode 100644 src/components/date_picker/super_date_picker/date_popover/relative_options.js create mode 100644 src/components/date_picker/super_date_picker/date_popover/relative_utils.js create mode 100644 src/components/date_picker/super_date_picker/date_popover/relative_utils.test.js diff --git a/src/components/date_picker/super_date_picker/date_button.js b/src/components/date_picker/super_date_picker/date_popover/date_button.js similarity index 82% rename from src/components/date_picker/super_date_picker/date_button.js rename to src/components/date_picker/super_date_picker/date_popover/date_button.js index a4237972b0a..d5b5078b46a 100644 --- a/src/components/date_picker/super_date_picker/date_button.js +++ b/src/components/date_picker/super_date_picker/date_popover/date_button.js @@ -2,7 +2,9 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import classNames from 'classnames'; -import { EuiPopover } from '../../popover'; +import { EuiPopover } from '../../../popover'; + +import { DateInput } from './date_input'; export class DateButton extends Component { static propTypes = { @@ -11,6 +13,8 @@ export class DateButton extends Component { needsUpdating: PropTypes.bool, buttonOnly: PropTypes.bool, value: PropTypes.string.isRequired, + onChange: PropTypes.func, + roundUp: PropTypes.bool, } state = { @@ -37,6 +41,8 @@ export class DateButton extends Component { value, buttonProps, buttonOnly, + roundUp, // eslint-disable-line no-unused-vars + onChange, // eslint-disable-line no-unused-vars ...rest } = this.props; @@ -82,7 +88,11 @@ export class DateButton extends Component { ownFocus {...rest} > -
Date selection popover
+ ); } diff --git a/src/components/date_picker/super_date_picker/date_popover/date_input.js b/src/components/date_picker/super_date_picker/date_popover/date_input.js new file mode 100644 index 00000000000..5ee1fa1680b --- /dev/null +++ b/src/components/date_picker/super_date_picker/date_popover/date_input.js @@ -0,0 +1,75 @@ + +import PropTypes from 'prop-types'; +import React from 'react'; + +import { EuiTabbedContent } from '../../../tabs'; + +import { + getDateMode, + DATE_MODES, + toAbsoluteString, + toRelativeString, +} from './date_modes'; + +export function DateInput({ value, roundUp, onChange }) { + + const onTabClick = (selectedTab) => { + switch(selectedTab.id) { + case DATE_MODES.ABSOLUTE: + onChange(toAbsoluteString(value, roundUp)); + break; + case DATE_MODES.RELATIVE: + onChange(toRelativeString(value)); + break; + case DATE_MODES.NOW: + onChange('now'); + break; + } + }; + + const renderTabs = () => { + return [ + { + id: DATE_MODES.ABSOLUTE, + name: 'Absolute', + content: ( +
absolute: {value}
+ ), + }, + { + id: DATE_MODES.RELATIVE, + name: 'Relative', + content: ( +
relative: {value}
+ ), + }, + { + id: DATE_MODES.NOW, + name: 'Now', + content: ( +
now: {value}
+ ), + } + ]; + }; + + return ( + + ); +} + +DateInput.propTypes = { + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + roundUp: PropTypes.bool, +}; + +DateInput.defaultProps = { + roundUp: false, +}; diff --git a/src/components/date_picker/super_date_picker/date_popover/date_modes.js b/src/components/date_picker/super_date_picker/date_popover/date_modes.js new file mode 100644 index 00000000000..5ab95e6b22a --- /dev/null +++ b/src/components/date_picker/super_date_picker/date_popover/date_modes.js @@ -0,0 +1,30 @@ + +import dateMath from '@elastic/datemath'; +import { parseRelativeParts, toRelativeStringFromParts } from './relative_utils'; + +export const DATE_MODES = { + ABSOLUTE: 'absolute', + RELATIVE: 'relative', + NOW: 'now', +}; + +export function getDateMode(value) { + if (value === 'now') { + return DATE_MODES.NOW; + } + + if (value.includes('now')) { + return DATE_MODES.RELATIVE; + } + + return DATE_MODES.absolute; +} + +export function toAbsoluteString(value, roundUp) { + return dateMath.parse(value, { roundUp }).toISOString(); +} + + +export function toRelativeString(value) { + return toRelativeStringFromParts(parseRelativeParts(value)); +} diff --git a/src/components/date_picker/super_date_picker/date_popover/relative_options.js b/src/components/date_picker/super_date_picker/date_popover/relative_options.js new file mode 100644 index 00000000000..43045a13a03 --- /dev/null +++ b/src/components/date_picker/super_date_picker/date_popover/relative_options.js @@ -0,0 +1,18 @@ + +export const relativeOptions = [ + { 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+' }, +]; diff --git a/src/components/date_picker/super_date_picker/date_popover/relative_utils.js b/src/components/date_picker/super_date_picker/date_popover/relative_utils.js new file mode 100644 index 00000000000..c77fc35de07 --- /dev/null +++ b/src/components/date_picker/super_date_picker/date_popover/relative_utils.js @@ -0,0 +1,61 @@ + +import dateMath from '@elastic/datemath'; +import moment from 'moment'; +import _ from 'lodash'; +import { relativeOptions } from './relative_options'; + +export function parseRelativeParts(value) { + const matches = _.isString(value) && value.match(/now(([\-\+])([0-9]+)([smhdwMy])(\/[smhdwMy])?)?/); + + const isNow = matches && !matches[1]; + const operator = matches && matches[2]; + const count = matches && matches[3]; + const unit = matches && matches[4]; + const roundBy = matches && matches[5]; + + if (isNow) { + return { count: 0, unit: 's', round: false }; + } + + if (count && unit) { + return { + count: parseInt(count, 10), + unit: operator === '+' ? `${unit}+` : unit, + round: roundBy ? true : false, + }; + } + + const results = { count: 0, unit: 's', round: false }; + const duration = moment.duration(moment().diff(dateMath.parse(value))); + const units = _.pluck(_.clone(relativeOptions).reverse(), 'value') + .filter(s => /^[smhdwMy]$/.test(s)); + let unitOp = ''; + for (let i = 0; i < units.length; i++) { + const as = duration.as(units[i]); + if (as < 0) unitOp = '+'; + if (Math.abs(as) > 1) { + results.count = Math.round(Math.abs(as)); + results.unit = units[i] + unitOp; + results.round = false; + break; + } + } + return results; +} + +export function toRelativeStringFromParts(relativeParts) { + const count = _.get(relativeParts, `count`, 0); + const round = _.get(relativeParts, `round`, false); + + if (count === 0 && !round) { + return 'now'; + } + + const matches = _.get(relativeParts, `unit`, 's').match(/([smhdwMy])(\+)?/); + const unit = matches[1]; + const operator = matches && matches[2] ? matches[2] : '-'; + + let result = `now${operator}${count}${unit}`; + result += (round ? `/${unit}` : ''); + return result; +} diff --git a/src/components/date_picker/super_date_picker/date_popover/relative_utils.test.js b/src/components/date_picker/super_date_picker/date_popover/relative_utils.test.js new file mode 100644 index 00000000000..8df1dbf074f --- /dev/null +++ b/src/components/date_picker/super_date_picker/date_popover/relative_utils.test.js @@ -0,0 +1,119 @@ + +import { parseRelativeParts, toRelativeStringFromParts } from './relative_utils'; +import moment from 'moment'; + +describe('parseRelativeParts', () => { + describe('relative', () => { + it('should parse now', () => { + const out = parseRelativeParts('now'); + expect(out).toEqual({ + count: 0, + unit: 's', + round: false + }); + }); + + it('should parse now-2h', () => { + const out = parseRelativeParts('now-2h'); + expect(out).toEqual({ + count: 2, + unit: 'h', + round: false + }); + }); + + it('should parse now-2h/h', () => { + const out = parseRelativeParts('now-2h/h'); + expect(out).toEqual({ + count: 2, + unit: 'h', + round: true + }); + }); + + it('should parse now+10m/m', () => { + const out = parseRelativeParts('now+10m/m'); + expect(out).toEqual({ + count: 10, + unit: 'm+', + round: true + }); + }); + }); + + describe('absolute', () => { + it('should parse now', () => { + const out = parseRelativeParts(moment().toDate().toISOString()); + expect(out).toEqual({ + count: 0, + unit: 's', + round: false + }); + }); + + it('should parse 3 months ago', () => { + const out = parseRelativeParts(moment().subtract(3, 'M').toDate().toISOString()); + expect(out).toEqual({ + count: 3, + unit: 'M', + round: false + }); + }); + + it('should parse 15 minutes ago', () => { + const out = parseRelativeParts(moment().subtract(15, 'm').toDate().toISOString()); + expect(out).toEqual({ + count: 15, + unit: 'm', + round: false + }); + }); + + it('should parse 2 hours from now', () => { + const out = parseRelativeParts(moment().add(2, 'h').toDate().toISOString()); + expect(out).toEqual({ + count: 2, + unit: 'h+', + round: false + }); + }); + }); +}); + +describe('toRelativeStringFromParts', () => { + it('should convert parts to now', () => { + const out = toRelativeStringFromParts({ + count: 0, + unit: 's', + round: false + }); + expect(out).toEqual('now'); + }); + + it('should convert parts to now-2h', () => { + const out = toRelativeStringFromParts({ + count: 2, + unit: 'h', + round: false + }); + expect(out).toEqual('now-2h'); + }); + + it('should convert parts to now-2h/h', () => { + const out = toRelativeStringFromParts({ + count: 2, + unit: 'h', + round: true + }); + expect(out).toEqual('now-2h/h'); + }); + + it('should convert parts to now+10m/m', () => { + const out = toRelativeStringFromParts({ + count: 10, + unit: 'm+', + round: true + }); + expect(out).toEqual('now+10m/m'); + }); +}); diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index 1765c637f2b..f1a7fb5c999 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -26,7 +26,7 @@ import { prettyDuration } from './pretty_duration'; import dateMath from '@elastic/datemath'; import { QuickSelectPopover } from './quick_select_popover/quick_select_popover'; -import { DateButton } from './date_button'; +import { DateButton } from './date_popover/date_button'; import { EuiDatePickerRange } from '../date_picker_range'; import { EuiFormControlLayout } from '../../form'; @@ -147,18 +147,21 @@ export class EuiSuperDatePicker extends Component { isCustom startDateControl={ } endDateControl={ } > From c8f3339601a4fefd8abfda991fb6c7fc024237d0 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 6 Dec 2018 09:02:05 -0700 Subject: [PATCH 09/37] clean up date_popover --- .../super_date_picker/_super_date_picker.scss | 4 +- ...{date_button.js => date_popover_button.js} | 27 ++++++------- ...{date_input.js => date_popover_content.js} | 14 +++---- .../super_date_picker/super_date_picker.js | 39 +++++++++---------- 4 files changed, 39 insertions(+), 45 deletions(-) rename src/components/date_picker/super_date_picker/date_popover/{date_button.js => date_popover_button.js} (76%) rename src/components/date_picker/super_date_picker/date_popover/{date_input.js => date_popover_content.js} (78%) diff --git a/src/components/date_picker/super_date_picker/_super_date_picker.scss b/src/components/date_picker/super_date_picker/_super_date_picker.scss index b8b48560225..16bdde5cf33 100644 --- a/src/components/date_picker/super_date_picker/_super_date_picker.scss +++ b/src/components/date_picker/super_date_picker/_super_date_picker.scss @@ -28,7 +28,7 @@ } } -.euiSuperDatePicker__dateButton { +.euiSuperDatePicker__dateText { @include euiFormControlText; display: block; width: 100%; @@ -37,7 +37,9 @@ height: $euiFormControlHeight - 2px; word-break: break-all; transition: background $euiAnimSpeedFast ease-in; +} +.euiSuperDatePicker__dateButton { $backgroundColor: tintOrShade($euiColorSuccess, 90%, 70%); $textColor: makeHighContrastColor($euiColorSuccess, $backgroundColor); diff --git a/src/components/date_picker/super_date_picker/date_popover/date_button.js b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js similarity index 76% rename from src/components/date_picker/super_date_picker/date_popover/date_button.js rename to src/components/date_picker/super_date_picker/date_popover/date_popover_button.js index d5b5078b46a..2c2388bf7aa 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_button.js +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js @@ -4,16 +4,15 @@ import classNames from 'classnames'; import { EuiPopover } from '../../../popover'; -import { DateInput } from './date_input'; +import { DatePopoverContent } from './date_popover_content'; -export class DateButton extends Component { +export class DatePopoverButton extends Component { static propTypes = { position: PropTypes.oneOf(['start', 'end']), isInvalid: PropTypes.bool, needsUpdating: PropTypes.bool, - buttonOnly: PropTypes.bool, value: PropTypes.string.isRequired, - onChange: PropTypes.func, + onValueChange: PropTypes.func, roundUp: PropTypes.bool, } @@ -40,13 +39,13 @@ export class DateButton extends Component { needsUpdating, value, buttonProps, - buttonOnly, - roundUp, // eslint-disable-line no-unused-vars - onChange, // eslint-disable-line no-unused-vars + roundUp, + onValueChange, ...rest } = this.props; const classes = classNames([ + 'euiSuperDatePicker__dateText', 'euiSuperDatePicker__dateButton', `euiSuperDatePicker__dateButton--${position}`, { @@ -65,7 +64,7 @@ export class DateButton extends Component { const button = ( ); diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.js b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.js index e18c404dc1d..b16ad706c57 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.js +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.js @@ -5,7 +5,8 @@ import React from 'react'; import { EuiTabbedContent } from '../../../tabs'; import { EuiText } from '../../../text'; -import { Relative } from './relative'; +import { AbsoluteTab } from './absolute_tab'; +import { RelativeTab } from './relative_tab'; import { getDateMode, @@ -36,14 +37,19 @@ export function DatePopoverContent({ value, roundUp, onChange, dateFormat }) { id: DATE_MODES.ABSOLUTE, name: 'Absolute', content: ( -
absolute: {value}
+ ), }, { id: DATE_MODES.RELATIVE, name: 'Relative', content: ( - + onClick={this.toggleEditMode} + > Show dates @@ -170,21 +169,17 @@ export class EuiSuperDatePicker extends Component { } renderUpdateButton = () => { - const color = this.state.hasChanged ? 'secondary' : 'primary'; - const icon = this.state.hasChanged ? 'kqlFunction' : 'refresh'; - let text = this.state.hasChanged ? 'Update' : 'Refresh'; - return ( - {text} + {this.state.hasChanged ? 'Update' : 'Refresh'} ); } From 9734df44a5b670bca3e8096bc0ffe98c09f04ec4 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 6 Dec 2018 12:44:01 -0700 Subject: [PATCH 12/37] implement step forward and step backward --- .../quick_select_popover/quick_select.js | 41 ++++++++++++++++++- .../quick_select_popover.js | 5 ++- .../super_date_picker/super_date_picker.js | 22 ++-------- 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js index 878ca227d67..d57e1b5d885 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js @@ -1,6 +1,8 @@ import PropTypes from 'prop-types'; import React, { Component, Fragment } from 'react'; +import moment from 'moment'; +import dateMath from '@elastic/datemath'; import { EuiButton, EuiButtonIcon } from '../../../button'; import { EuiFlexGroup, EuiFlexItem } from '../../../flex'; @@ -70,6 +72,31 @@ export class QuickSelect extends Component { }); } + getBounds = () => { + return { + min: dateMath.parse(this.props.from), + max: dateMath.parse(this.props.to, { roundUp: true }), + }; + } + + stepForward = () => { + const { min, max } = this.getBounds(); + const diff = max.diff(min); + this.props.applyTime({ + from: moment(max).add(1, 'ms').toISOString(), + to: moment(max).add(diff + 1, 'ms').toISOString(), + }); + } + + stepBackward = () => { + const { min, max } = this.getBounds(); + const diff = max.diff(min); + this.props.applyTime({ + from: moment(min).subtract(diff + 1, 'ms').toISOString(), + to: moment(min).subtract(1, 'ms').toISOString(), + }); + } + render() { return ( @@ -79,12 +106,20 @@ export class QuickSelect extends Component { - + - + @@ -140,4 +175,6 @@ export class QuickSelect extends Component { QuickSelect.propTypes = { applyTime: PropTypes.func.isRequired, + from: PropTypes.string.isRequired, + to: PropTypes.string.isRequired, }; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js index 65c67586286..55b891ef339 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js @@ -71,7 +71,8 @@ export class QuickSelectPopover extends Component {
{ - return calculateBounds({ from: this.state.from, to: this.state.to }); - } - - stepForward = () => { - this.setTime(timeNavigation.stepForward(this.getBounds())); - } - - stepBackward = () => { - this.setTime(timeNavigation.stepBackward(this.getBounds())); - }*/ - applyTime = () => { this.props.onTimeChange({ from: this.state.from, to: this.state.to }); } @@ -188,8 +172,8 @@ export class EuiSuperDatePicker extends Component { const quickSelect = ( Date: Thu, 6 Dec 2018 13:52:52 -0700 Subject: [PATCH 13/37] add data-test-subjs needed for existing kibana functional tests --- .../super_date_picker/date_popover/absolute_tab.js | 1 + .../super_date_picker/date_popover/date_popover_button.js | 1 + .../super_date_picker/date_popover/date_popover_content.js | 3 +++ .../quick_select_popover/commonly_used.js | 7 ++++++- .../quick_select_popover/quick_select_popover.js | 6 +++++- .../quick_select_popover/refresh_interval.js | 3 +++ .../date_picker/super_date_picker/super_date_picker.js | 2 ++ 7 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js index 4cf60865a91..cabf2b4dfd9 100644 --- a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js +++ b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js @@ -71,6 +71,7 @@ export class AbsoluteTab extends Component { isInvalid={this.state.isTextInvalid} value={this.state.textInputValue} onChange={this.handleTextChange} + data-test-subj={`superDatePickerAbsoluteDateInput`} />
diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js index e5bbfe33f2a..83b933ae9c0 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js @@ -70,6 +70,7 @@ export class DatePopoverButton extends Component { onClick={this.togglePopover} className={classes} title={title} + data-test-subj={`superDatePicker${this.props.position}DatePopoverButton`} {...buttonProps} > {formatTimeString(value, dateFormat)} diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.js b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.js index b16ad706c57..f38660dbda1 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.js +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.js @@ -44,6 +44,7 @@ export function DatePopoverContent({ value, roundUp, onChange, dateFormat }) { roundUp={roundUp} /> ), + 'data-test-subj': 'superDatePickerAbsoluteTab', }, { id: DATE_MODES.RELATIVE, @@ -55,6 +56,7 @@ export function DatePopoverContent({ value, roundUp, onChange, dateFormat }) { onChange={onChange} /> ), + 'data-test-subj': 'superDatePickerRelativeTab', }, { id: DATE_MODES.NOW, @@ -67,6 +69,7 @@ export function DatePopoverContent({ value, roundUp, onChange, dateFormat }) {

), + 'data-test-subj': 'superDatePickerNowTab', } ]; }; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js b/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js index 64ae8961180..f7330ab6861 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js @@ -17,7 +17,12 @@ export function CommonlyUsed({ applyTime, commonlyUsedRanges }) { }; return ( - {label} + + {label} + ); }); diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js index 55b891ef339..88ceadf2d9c 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js @@ -54,6 +54,7 @@ export class QuickSelectPopover extends Component { size="xs" iconType="arrowDown" iconSide="right" + data-test-subj="superDatePickerToggleQuickMenuButton" > @@ -68,7 +69,10 @@ export class QuickSelectPopover extends Component { anchorPosition="downLeft" ownFocus > -
+
@@ -108,6 +109,7 @@ export class RefreshInterval extends Component { value={this.state.units} options={refreshUnitsOptions} onChange={this.onUnitsChange} + data-test-subj="superDatePickerRefreshIntervalUnitsSelect" /> @@ -119,6 +121,7 @@ export class RefreshInterval extends Component { onClick={this.toogleRefresh} style={{ minWidth: 90 }} disabled={this.state.value === '' || this.state.value <= 0} + data-test-subj="superDatePickerToggleRefreshButton" > {this.props.isPaused ? 'Start' : 'Stop'} diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index 7d314431de8..a17d6857e33 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -115,6 +115,7 @@ export class EuiSuperDatePicker extends Component { size="xs" style={{ flexGrow: 0 }} onClick={this.toggleEditMode} + data-test-subj="superDatePickerShowDatesButton" > Show dates @@ -162,6 +163,7 @@ export class EuiSuperDatePicker extends Component { textProps={{ className: 'euiSuperDatePicker__updateButtonText' }} disabled={this.state.isInvalid} onClick={this.applyTime} + data-test-subj="superDatePickerApplyTimeButton" > {this.state.hasChanged ? 'Update' : 'Refresh'} From 78583b66094d1b6492931dcd05a56470a3cb6794 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 6 Dec 2018 14:07:05 -0700 Subject: [PATCH 14/37] remove global date picker pattern, add comments about EuiSuperDatePicker props --- src-docs/src/theme_dark.scss | 1 - src-docs/src/theme_k6_dark.scss | 1 - src-docs/src/theme_k6_light.scss | 1 - src-docs/src/theme_light.scss | 1 - .../date_picker/_global_date_picker.scss | 97 --- .../views/date_picker/date_picker_example.js | 27 +- .../views/date_picker/global_date_picker.js | 566 ------------------ .../super_date_picker/super_date_picker.js | 19 + 8 files changed, 21 insertions(+), 692 deletions(-) delete mode 100644 src-docs/src/views/date_picker/_global_date_picker.scss delete mode 100644 src-docs/src/views/date_picker/global_date_picker.js diff --git a/src-docs/src/theme_dark.scss b/src-docs/src/theme_dark.scss index 97d4ef0207c..f09a1aede5c 100644 --- a/src-docs/src/theme_dark.scss +++ b/src-docs/src/theme_dark.scss @@ -1,4 +1,3 @@ @import '../../src/theme_dark'; @import './components/guide_components'; @import './views/header/global_filter_group'; -@import './views/date_picker/global_date_picker'; diff --git a/src-docs/src/theme_k6_dark.scss b/src-docs/src/theme_k6_dark.scss index a3738416b1d..99e708a00f1 100644 --- a/src-docs/src/theme_k6_dark.scss +++ b/src-docs/src/theme_k6_dark.scss @@ -1,4 +1,3 @@ @import '../../src/theme_k6_dark'; @import './components/guide_components'; @import './views/header/global_filter_group'; -@import './views/date_picker/global_date_picker'; diff --git a/src-docs/src/theme_k6_light.scss b/src-docs/src/theme_k6_light.scss index 6fe9d55fe4b..544617f335d 100644 --- a/src-docs/src/theme_k6_light.scss +++ b/src-docs/src/theme_k6_light.scss @@ -1,4 +1,3 @@ @import '../../src/theme_k6_light'; @import './components/guide_components'; @import './views/header/global_filter_group'; -@import './views/date_picker/global_date_picker'; diff --git a/src-docs/src/theme_light.scss b/src-docs/src/theme_light.scss index bd607c9114f..782fc422f63 100644 --- a/src-docs/src/theme_light.scss +++ b/src-docs/src/theme_light.scss @@ -1,4 +1,3 @@ @import '../../src/theme_light'; @import './components/guide_components'; @import './views/header/global_filter_group'; -@import './views/date_picker/global_date_picker'; diff --git a/src-docs/src/views/date_picker/_global_date_picker.scss b/src-docs/src/views/date_picker/_global_date_picker.scss deleted file mode 100644 index d522ce46bcb..00000000000 --- a/src-docs/src/views/date_picker/_global_date_picker.scss +++ /dev/null @@ -1,97 +0,0 @@ -//// GLOBAL Date picker - -// sass-lint:disable no-important -.euiGlobalDatePicker__quickSelectButton { - // Override prepend border since button already lives inside another prepend - border-right: none !important; - - .euiGlobalDatePicker__quickSelectButtonText { - // Override specificity from universal and sibiling selectors - margin-right: $euiSizeXS !important; - } -} - -.euiGlobalDatePicker.euiFormControlLayout { - max-width: 480px; - - > .euiFormControlLayout__childrenWrapper { - flex: 1 1 100%; - overflow: hidden; - - > .euiDatePickerRange { - max-width: none; - width: auto; - - // sass-lint:disable nesting-depth - .euiPopover__anchor { - display: block; - } - } - } -} - -.euiGlobalDatePicker__dateButton { - @include euiFormControlText; - display: block; - width: 100%; - padding: 0 $euiSizeS; - line-height: $euiFormControlHeight - 2px; - height: $euiFormControlHeight - 2px; - word-break: break-all; - transition: background $euiAnimSpeedFast ease-in; - - $backgroundColor: tintOrShade($euiColorSuccess, 90%, 70%); - $textColor: makeHighContrastColor($euiColorSuccess, $backgroundColor); - - &-isSelected, - &-needsUpdating, - &:hover, - &:focus { - background-color: $backgroundColor; - } - - &-needsUpdating { - color: $textColor; - } - - &-isInvalid { - $backgroundColor: tintOrShade($euiColorDanger, 90%, 70%); - $textColor: makeHighContrastColor($euiColorDanger, $backgroundColor); - background-color: $backgroundColor; - color: $textColor; - } - - .euiFormControlLayout__prepend { - border: none; - } -} - -.euiGlobalDatePicker__dateButton--start { - text-align: right; -} - -.euiGlobalDatePicker__dateButton--end { - text-align: left; -} - -.euiGlobalDatePicker__updateButton { - // Just wide enough for all 3 states - min-width: $euiButtonMinWidth + ($euiSizeXS * 1.5); -} - -.euiGlobalDatePicker__popoverSection { - @include euiScrollBar; - max-height: $euiSizeM * 11; - overflow: hidden; - overflow-y: auto; -} - -@include euiBreakpoint('xs', 's') { - .euiGlobalDatePicker__updateButton { - min-width: 0; - - .euiGlobalDatePicker__updateButtonText { - display: none; - } - } -} diff --git a/src-docs/src/views/date_picker/date_picker_example.js b/src-docs/src/views/date_picker/date_picker_example.js index 7235470eee8..94c6ea9078d 100644 --- a/src-docs/src/views/date_picker/date_picker_example.js +++ b/src-docs/src/views/date_picker/date_picker_example.js @@ -11,7 +11,7 @@ import { EuiLink, EuiDatePicker, EuiDatePickerRange, - EuiCallOut, + EuiSuperDatePicker, } from '../../../../src/components'; import DatePicker from './date_picker'; @@ -62,10 +62,6 @@ import SuperDatePicker from './super_date_picker'; const superDatePickerSource = require('!!raw-loader!./super_date_picker'); const superDatePickerHtml = renderToHtml(SuperDatePicker); -import GlobalDatePicker from './global_date_picker'; -const globalDatePickerSource = require('!!raw-loader!./global_date_picker'); -const globalDatePickerHtml = renderToHtml(GlobalDatePicker); - export const DatePickerExample = { title: 'DatePicker', sections: [{ @@ -284,25 +280,6 @@ export const DatePickerExample = {
), demo: , - }, { - title: 'Global date picker', - source: [{ - type: GuideSectionTypes.JS, - code: globalDatePickerSource, - }, { - type: GuideSectionTypes.HTML, - code: globalDatePickerHtml, - }], - text: ( -
- -

- This documents a visual pattern for the eventual replacement of Kibana's - global date/time picker. It uses all EUI components with some custom styles. -

-
-
- ), - demo: , + props: { EuiSuperDatePicker }, }], }; diff --git a/src-docs/src/views/date_picker/global_date_picker.js b/src-docs/src/views/date_picker/global_date_picker.js deleted file mode 100644 index 13930214175..00000000000 --- a/src-docs/src/views/date_picker/global_date_picker.js +++ /dev/null @@ -1,566 +0,0 @@ - -import React, { - Component, Fragment, -} from 'react'; -import PropTypes from 'prop-types'; - -import moment from 'moment'; -import classNames from 'classnames'; - -import { - EuiDatePicker, - EuiDatePickerRange, - EuiFormControlLayout, - EuiButtonEmpty, - EuiIcon, - EuiLink, EuiTitle, EuiFlexGrid, EuiFlexItem, - EuiPopover, - EuiSpacer, - EuiText, - EuiHorizontalRule, - EuiFlexGroup, - EuiFormRow, - EuiSelect, - EuiFieldNumber, - EuiButton, - EuiTabbedContent, - EuiForm, - EuiSwitch, - EuiToolTip, - EuiFieldText, - EuiButtonIcon, -} from '../../../../src/components'; - -const commonDates = [ - 'Today', 'Yesterday', 'This week', 'Week to date', 'This month', 'Month to date', 'This year', 'Year to date', -]; - -const relativeSelectOptions = [ - { text: 'Seconds ago', value: 'string:s' }, - { text: 'Minutes ago', value: 'string:m' }, - { text: 'Hours ago', value: 'string:h' }, - { text: 'Days ago', value: 'string:d' }, - { text: 'Weeks ago', value: 'string:w' }, - { text: 'Months ago', value: 'string:M' }, - { text: 'Years ago', value: 'string:y' }, - { text: 'Seconds from now', value: 'string:s+' }, - { text: 'Minutes from now', value: 'string:m+' }, - { text: 'Hours from now', value: 'string:h+' }, - { text: 'Days from now', value: 'string:d+' }, - { text: 'Weeks from now', value: 'string:w+' }, - { text: 'Months from now', value: 'string:M+' }, - { text: 'Years from now', value: 'string:y+' }, -]; - -class GlobalDatePopover extends Component { - constructor(props) { - super(props); - - this.tabs = [{ - id: 'absolute', - name: 'Absolute', - content: ( -
- - - - -
- ), - }, { - id: 'relative', - name: 'Relative', - content: ( - - - - - - - - - - - - - - - - - - - - - ), - }, { - id: 'now', - name: 'Now', - content: ( - -

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

-
- ), - }]; - - this.state = { - selectedTab: this.tabs[0], - }; - } - - onTabClick = (selectedTab) => { - this.setState({ selectedTab }); - }; - - render() { - return ( - - ); - } -} - -// eslint-disable-next-line react/no-multi-comp -class GlobalDateButton extends Component { - static propTypes = { - position: PropTypes.oneOf(['start', 'end']), - isInvalid: PropTypes.bool, - needsUpdating: PropTypes.bool, - buttonOnly: PropTypes.bool, - date: PropTypes.string, - } - - constructor(props) { - super(props); - - this.state = { - isPopoverOpen: false, - }; - } - - togglePopover = () => { - this.setState({ - isPopoverOpen: !this.state.isPopoverOpen, - }); - } - - closePopover = () => { - this.setState({ - isPopoverOpen: false, - }); - } - - render() { - const { - position, - isInvalid, - needsUpdating, - date, - buttonProps, - buttonOnly, - ...rest - } = this.props; - - const { - isPopoverOpen, - } = this.state; - - const classes = classNames([ - 'euiGlobalDatePicker__dateButton', - `euiGlobalDatePicker__dateButton--${position}`, - { - 'euiGlobalDatePicker__dateButton-isSelected': isPopoverOpen, - 'euiGlobalDatePicker__dateButton-isInvalid': isInvalid, - 'euiGlobalDatePicker__dateButton-needsUpdating': needsUpdating - } - ]); - - let title = date; - if (isInvalid) { - title = `Invalid date: ${title}`; - } else if (needsUpdating) { - title = `Update needed: ${title}`; - } - - const button = ( - - ); - - return buttonOnly ? button : ( - - - - ); - } -} - -// eslint-disable-next-line react/no-multi-comp -export default class extends Component { - constructor(props) { - super(props); - - this.state = { - startDate: moment().format('MMM DD YYYY h:mm:ss.SSS'), - endDate: moment().add(11, 'd').format('MMM DD YYYY hh:mm:ss.SSS'), - isPopoverOpen: false, - showPrettyFormat: false, - showNeedsUpdate: false, - isUpdating: false, - timerIsOn: false, - recentlyUsed: [ - ['11/25/2017 00:00 AM', '11/25/2017 11:59 PM'], - ['3 hours ago', '4 minutes ago'], - 'Last 6 months', - ['06/11/2017 06:11 AM', '06/11/2017 06:11 PM'], - ], - }; - } - - setTootipRef = node => (this.tooltip = node); - - showTooltip = () => this.tooltip.showToolTip(); - hideTooltip = () => this.tooltip.hideToolTip(); - - togglePopover = (e) => { - // HACK TODO: - // this works because react listens to all events at the - // document level, and you need to interact with the native - // event's propagation to short-circuit outside click handler - // see also: https://stackoverflow.com/a/24421834 - e.nativeEvent.stopImmediatePropagation(); - - this.setState(prevState => ({ - isPopoverOpen: !prevState.isPopoverOpen, - })); - } - - togglePrettyFormat = () => { - this.setState(prevState => ({ - showPrettyFormat: !prevState.showPrettyFormat, - })); - } - - toggleNeedsUpdate = () => { - this.setState(prevState => { - - if (!prevState.showNeedsUpdate) { - clearTimeout(this.tooltipTimeout); - this.showTooltip(); - this.tooltipTimeout = setTimeout(() => { - this.hideTooltip(); - }, 10000); - } - - return ({ - showNeedsUpdate: !prevState.showNeedsUpdate, - }); - }); - } - - closePopover = () => { - this.setState({ - isPopoverOpen: false, - }); - } - - toggleTimer = () => { - this.setState(prevState => ({ - timerIsOn: !prevState.timerIsOn, - })); - } - - toggleIsUpdating = () => { - this.setState(prevState => ({ - isUpdating: !prevState.isUpdating, - })); - } - - - render() { - const quickSelectButton = ( - - - - ); - - const commonlyUsed = this.renderCommonlyUsed(commonDates); - const recentlyUsed = this.renderRecentlyUsed(this.state.recentlyUsed); - - const quickSelectPopover = ( - -
- {this.renderQuickSelect()} - - {commonlyUsed} - - {recentlyUsed} - - {this.renderTimer()} -
-
- ); - - return ( - -   -   - - - - - - - - } - endDateControl={ - - } - > - {this.state.showPrettyFormat && - - - Show dates - - } - - - - - {this.renderUpdateButton()} - - - - ); - } - - renderUpdateButton = () => { - const color = this.state.showNeedsUpdate ? 'secondary' : 'primary'; - const icon = this.state.showNeedsUpdate ? 'kqlFunction' : 'refresh'; - let text = this.state.showNeedsUpdate ? 'Update' : 'Refresh'; - - if (this.state.isUpdating) { - text = 'Updating'; - } - - return ( - - - {text} - - - ); - } - - renderQuickSelect = () => { - const firstOptions = [ - { value: 'last', text: 'Last' }, - { value: 'previous', text: 'Previous' }, - ]; - - const lastOptions = [ - { value: 'seconds', text: 'seconds' }, - { value: 'minutes', text: 'minutes' }, - { value: 'hours', text: 'hours' }, - { value: 'days', text: 'days' }, - { value: 'weeks', text: 'weeks' }, - { value: 'months', text: 'months' }, - { value: 'years', text: 'years' }, - ]; - - return ( - - - - Quick select - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Apply - - - - - ); - } - - renderCommonlyUsed = (commonDates) => { - const links = commonDates.map((date) => { - return ( - {date} - ); - }); - - return ( - - Commonly used - - - - {links} - - - - ); - } - - renderRecentlyUsed = (recentDates) => { - const links = recentDates.map((date) => { - let dateRange; - if (typeof date !== 'string') { - dateRange = `${date[0]} – ${date[1]}`; - } - - return ( - {dateRange || date} - ); - }); - - return ( - - Recently used date ranges - - - - {links} - - - - ); - } - - renderTimer = () => { - const lastOptions = [ - { value: 'minutes', text: 'minutes' }, - { value: 'hours', text: 'hours' }, - ]; - - return ( - - Refresh every - - - - - - - - - - - - - - - - {this.state.timerIsOn ? 'Stop' : 'Start'} - - - - - - ); - } - -} diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index a17d6857e33..6b2c20d36ae 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -206,15 +206,34 @@ export class EuiSuperDatePicker extends Component { } EuiSuperDatePicker.propTypes = { + /** + * String as either datemath (e.g.: now, now-15m, now-15m/m) or + * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.sssZ' + */ from: PropTypes.string, + /** + * String as either datemath (e.g.: now, now-15m, now-15m/m) or + * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.sssZ' + */ to: PropTypes.string, onTimeChange: PropTypes.func.isRequired, isPaused: PropTypes.bool, + /** + * Refresh interval in milliseconds + */ refreshInterval: PropTypes.number, onRefreshChange: PropTypes.func.isRequired, + /** + * 'from' and 'to' must be string as either datemath (e.g.: now, now-15m, now-15m/m) or + * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.sssZ' + */ commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRangeShape), dateFormat: PropTypes.string, + /** + * 'from' and 'to' must be string as either datemath (e.g.: now, now-15m, now-15m/m) or + * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.sssZ' + */ recentlyUsedRanges: PropTypes.arrayOf(recentlyUsedRangeShape), }; From c2fd0e8c1dccd1df05a9c93f6f830c793a830c56 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 6 Dec 2018 14:11:40 -0700 Subject: [PATCH 15/37] change log --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37ad1815de2..d70ec37dce9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## [`master`](https://github.com/elastic/eui/tree/master) -No public interface changes since `5.3.0`. +- Added `EuiSuperDatePicker` component ([#1351](https://github.com/elastic/eui/pull/1351)) ## [`5.3.0`](https://github.com/elastic/eui/tree/v5.3.0) From 3e69d699dcb053339303fc4be128df4456e2ba4e Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 6 Dec 2018 14:21:25 -0700 Subject: [PATCH 16/37] remove kibana file header --- .../super_date_picker/super_date_picker.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index 6b2c20d36ae..3cc4ee7f51a 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -1,21 +1,3 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ import PropTypes from 'prop-types'; import React, { Component } from 'react'; From 48294f59ad34b9f6eb2603b1a285ecea92855c97 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 7 Dec 2018 09:49:47 -0700 Subject: [PATCH 17/37] only show pretty duration for commonly used ranges and relative to now, now to now isInvalid, recenlty selected in reverse order --- .../views/date_picker/super_date_picker.js | 2 +- .../{date_popover => }/date_modes.js | 0 .../date_popover/date_popover_content.js | 2 +- .../date_popover/relative_tab.js | 4 +- .../super_date_picker/pretty_duration.js | 14 ++++ .../super_date_picker/pretty_duration.test.js | 76 ++++++++++++------- .../{date_popover => }/relative_options.js | 0 .../{date_popover => }/relative_utils.js | 0 .../{date_popover => }/relative_utils.test.js | 0 .../super_date_picker/super_date_picker.js | 38 ++++++---- 10 files changed, 92 insertions(+), 44 deletions(-) rename src/components/date_picker/super_date_picker/{date_popover => }/date_modes.js (100%) rename src/components/date_picker/super_date_picker/{date_popover => }/relative_options.js (100%) rename src/components/date_picker/super_date_picker/{date_popover => }/relative_utils.js (100%) rename src/components/date_picker/super_date_picker/{date_popover => }/relative_utils.test.js (100%) diff --git a/src-docs/src/views/date_picker/super_date_picker.js b/src-docs/src/views/date_picker/super_date_picker.js index 6bf6ad66c7c..71458f619cd 100644 --- a/src-docs/src/views/date_picker/super_date_picker.js +++ b/src-docs/src/views/date_picker/super_date_picker.js @@ -17,7 +17,7 @@ export default class extends Component { const isDuplicate = recentlyUsedRange.from === from && recentlyUsedRange.to === to; return !isDuplicate; }); - recentlyUsedRanges.push({ from, to }); + recentlyUsedRanges.unshift({ from, to }); return { from, to, diff --git a/src/components/date_picker/super_date_picker/date_popover/date_modes.js b/src/components/date_picker/super_date_picker/date_modes.js similarity index 100% rename from src/components/date_picker/super_date_picker/date_popover/date_modes.js rename to src/components/date_picker/super_date_picker/date_modes.js diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.js b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.js index f38660dbda1..df2b5b88c26 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.js +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.js @@ -13,7 +13,7 @@ import { DATE_MODES, toAbsoluteString, toRelativeString, -} from './date_modes'; +} from '../date_modes'; export function DatePopoverContent({ value, roundUp, onChange, dateFormat }) { diff --git a/src/components/date_picker/super_date_picker/date_popover/relative_tab.js b/src/components/date_picker/super_date_picker/date_popover/relative_tab.js index ad616a7f27c..3d001845a48 100644 --- a/src/components/date_picker/super_date_picker/date_popover/relative_tab.js +++ b/src/components/date_picker/super_date_picker/date_popover/relative_tab.js @@ -14,8 +14,8 @@ import { } from '../../../form'; import { timeUnits } from '../time_units'; -import { relativeOptions } from './relative_options'; -import { parseRelativeParts, toRelativeStringFromParts } from './relative_utils'; +import { relativeOptions } from '../relative_options'; +import { parseRelativeParts, toRelativeStringFromParts } from '../relative_utils'; export class RelativeTab extends Component { diff --git a/src/components/date_picker/super_date_picker/pretty_duration.js b/src/components/date_picker/super_date_picker/pretty_duration.js index 2f3fbb9e6af..cd2680db477 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.js +++ b/src/components/date_picker/super_date_picker/pretty_duration.js @@ -2,6 +2,7 @@ import dateMath from '@elastic/datemath'; import moment from 'moment'; import { timeUnits } from './time_units'; +import { getDateMode, DATE_MODES } from './date_modes'; const ISO_FORMAT = 'YYYY-MM-DDTHH:mm:ss.sssZ'; @@ -50,3 +51,16 @@ export function prettyDuration(timeFrom, timeTo, quickRanges = [], dateFormat) { return cantLookup(timeFrom, timeTo, dateFormat); } + +export function showPrettyDuration(timeFrom, timeTo, quickRanges = []) { + const matchingQuickRange = quickRanges.find(({ from: quickFrom, to: quickTo }) => { + return timeFrom === quickFrom && timeTo === quickTo; + }); + if (matchingQuickRange) { + return true; + } + + const fromDateMode = getDateMode(timeFrom); + const toDateMode = getDateMode(timeTo); + return fromDateMode === DATE_MODES.RELATIVE && toDateMode === DATE_MODES.NOW; +} diff --git a/src/components/date_picker/super_date_picker/pretty_duration.test.js b/src/components/date_picker/super_date_picker/pretty_duration.test.js index 9e4ea99c506..af960f18f3f 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.test.js +++ b/src/components/date_picker/super_date_picker/pretty_duration.test.js @@ -1,6 +1,6 @@ import moment from 'moment-timezone'; -import { prettyDuration } from './pretty_duration'; +import { prettyDuration, showPrettyDuration } from './pretty_duration'; moment.tz.setDefault('UTC'); const dateFormat = 'MMMM Do YYYY, HH:mm:ss.SSS'; @@ -12,33 +12,57 @@ const quickRanges = [ } ]; -test('quick range', () => { - const timeFrom = 'now-15m'; - const timeTo = 'now'; - expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('quick range 15 minutes custom display'); -}); +describe('prettyDuration', () => { + test('quick range', () => { + const timeFrom = 'now-15m'; + const timeTo = 'now'; + expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('quick range 15 minutes custom display'); + }); -test('last', () => { - const timeFrom = 'now-1M'; - const timeTo = 'now'; - expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('Last 1M'); -}); + test('last', () => { + const timeFrom = 'now-1M'; + const timeTo = 'now'; + expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('Last 1M'); + }); -test('last that is rounded', () => { - const timeFrom = 'now-1M/w'; - const timeTo = 'now'; - expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('Last 1M rounded to the week'); -}); + test('last that is rounded', () => { + const timeFrom = 'now-1M/w'; + const timeTo = 'now'; + expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('Last 1M rounded to the week'); + }); -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'); + 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'); + }); + + // TODO figure out timezone to get this working + //test('absolute dates', () => { + // const timeFrom = '2018-01-17T18:57:57.149Z'; + // const timeTo = '2018-01-17T20:00:00.000Z'; + // expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('January 17th 2018, 18:57:57.149 to January 17th 2018, 20:00:00.000'); + //}); }); -// TODO figure out timezone to get this working -//test('absolute dates', () => { -// const timeFrom = '2018-01-17T18:57:57.149Z'; -// const timeTo = '2018-01-17T20:00:00.000Z'; -// expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('January 17th 2018, 18:57:57.149 to January 17th 2018, 20:00:00.000'); -//}); +describe('showPrettyDuration', () => { + test('should show pretty duration for quick range', () => { + expect(showPrettyDuration('now-15m', 'now', quickRanges)).toBe(true); + }); + + test('should show pretty duration for relative to now', () => { + expect(showPrettyDuration('now-17m', 'now', quickRanges)).toBe(true); + }); + + test('should not show pretty duration for relative to relative', () => { + expect(showPrettyDuration('now-17m', 'now-3m', quickRanges)).toBe(false); + }); + + test('should not show pretty duration for absolute to absolute', () => { + expect(showPrettyDuration('2018-01-17T18:57:57.149Z', '2018-01-17T20:00:00.000Z', quickRanges)).toBe(false); + }); + + test('should not show pretty duration for absolute to now', () => { + expect(showPrettyDuration('2018-01-17T18:57:57.149Z', 'now', quickRanges)).toBe(false); + }); +}); diff --git a/src/components/date_picker/super_date_picker/date_popover/relative_options.js b/src/components/date_picker/super_date_picker/relative_options.js similarity index 100% rename from src/components/date_picker/super_date_picker/date_popover/relative_options.js rename to src/components/date_picker/super_date_picker/relative_options.js diff --git a/src/components/date_picker/super_date_picker/date_popover/relative_utils.js b/src/components/date_picker/super_date_picker/relative_utils.js similarity index 100% rename from src/components/date_picker/super_date_picker/date_popover/relative_utils.js rename to src/components/date_picker/super_date_picker/relative_utils.js diff --git a/src/components/date_picker/super_date_picker/date_popover/relative_utils.test.js b/src/components/date_picker/super_date_picker/relative_utils.test.js similarity index 100% rename from src/components/date_picker/super_date_picker/date_popover/relative_utils.test.js rename to src/components/date_picker/super_date_picker/relative_utils.test.js diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index 3cc4ee7f51a..94892e92b3c 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { commonlyUsedRangeShape, recentlyUsedRangeShape } from './types'; -import { prettyDuration } from './pretty_duration'; +import { prettyDuration, showPrettyDuration } from './pretty_duration'; import dateMath from '@elastic/datemath'; @@ -19,36 +19,46 @@ export class EuiSuperDatePicker extends Component { constructor(props) { super(props); + const { + from, + to, + commonlyUsedRanges + } = this.props; + this.state = { - from: this.props.from, - to: this.props.to, + from, + to, isInvalid: false, hasChanged: false, - isEditMode: false, + showPrettyDuration: showPrettyDuration(from, to, commonlyUsedRanges), }; } static getDerivedStateFromProps = (nextProps) => { + const { + from, + to, + commonlyUsedRanges + } = nextProps; + return { - from: nextProps.from, - to: nextProps.to, + from, + to, isInvalid: false, - errorMessage: undefined, hasChanged: false, - isEditMode: false, + showPrettyDuration: showPrettyDuration(from, to, commonlyUsedRanges), }; } setTime = ({ from, to }) => { const fromMoment = dateMath.parse(from); const toMoment = dateMath.parse(to, { roundUp: true }); - const isInvalid = fromMoment.isAfter(toMoment); + const isInvalid = (from === 'now' && to === 'now') || fromMoment.isAfter(toMoment); this.setState({ from, to, isInvalid, - errorMessage: isInvalid ? 'Invalid time range, "from" must occur before "to"' : undefined, hasChanged: true, }); } @@ -69,8 +79,8 @@ export class EuiSuperDatePicker extends Component { this.props.onTimeChange({ from, to }); } - toggleEditMode = () => { - this.setState({ isEditMode: true }); + hidePrettyDuration = () => { + this.setState({ showPrettyDuration: false }); } renderDatePickerRange = () => { @@ -81,7 +91,7 @@ export class EuiSuperDatePicker extends Component { isInvalid, } = this.state; - if (!this.state.isEditMode) { + if (this.state.showPrettyDuration) { return ( Show dates From ebd53922c1bfbb8652c2404ab85aedb6d87ca66c Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 7 Dec 2018 11:13:19 -0700 Subject: [PATCH 18/37] use full time unit name in pretty duration --- .../super_date_picker/pretty_duration.js | 28 ++++++++++++------- .../super_date_picker/pretty_duration.test.js | 6 ++-- .../super_date_picker/relative_utils.js | 15 ++++++---- .../super_date_picker/relative_utils.test.js | 6 ++-- .../super_date_picker/time_units.js | 1 - 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/components/date_picker/super_date_picker/pretty_duration.js b/src/components/date_picker/super_date_picker/pretty_duration.js index cd2680db477..8ac5c060436 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.js +++ b/src/components/date_picker/super_date_picker/pretty_duration.js @@ -3,6 +3,7 @@ import dateMath from '@elastic/datemath'; import moment from 'moment'; import { timeUnits } from './time_units'; import { getDateMode, DATE_MODES } from './date_modes'; +import { parseRelativeParts } from './relative_utils'; const ISO_FORMAT = 'YYYY-MM-DDTHH:mm:ss.sssZ'; @@ -12,6 +13,12 @@ function cantLookup(timeFrom, timeTo, dateFormat) { return `${displayFrom} to ${displayTo}`; } +function isRelativeToNow(timeFrom, timeTo) { + const fromDateMode = getDateMode(timeFrom); + const toDateMode = getDateMode(timeTo); + return fromDateMode === DATE_MODES.RELATIVE && toDateMode === DATE_MODES.NOW; +} + export function formatTimeString(timeString, dateFormat, roundUp = false) { const timeAsMoment = moment(timeString, ISO_FORMAT, true); if (timeAsMoment.isValid()) { @@ -38,13 +45,16 @@ export function prettyDuration(timeFrom, timeTo, quickRanges = [], dateFormat) { return matchingQuickRange.label; } - const fromParts = timeFrom.split('-'); - if (timeTo === 'now' && fromParts[0] === 'now' && fromParts[1]) { - const rounded = fromParts[1].split('/'); - let text = `Last ${rounded[0]}`; - if (rounded[1]) { - const timeUnit = timeUnits[rounded[1]] ? timeUnits[rounded[1]] : rounded[1]; - text = `${text} rounded to the ${timeUnit}`; + if (isRelativeToNow(timeFrom, timeTo)) { + const relativeParts = parseRelativeParts(timeFrom); + let countTimeUnit = timeUnits[relativeParts.unit.substring(0, 1)]; + if (relativeParts.count > 1) { + countTimeUnit += 's'; + } + let text = `Last ${relativeParts.count} ${countTimeUnit}`; + if (relativeParts.round) { + const roundTimeUnit = timeUnits[relativeParts.roundUnit] ? timeUnits[relativeParts.roundUnit] : relativeParts.roundUnit; + text += ` rounded to the ${roundTimeUnit}`; } return text; } @@ -60,7 +70,5 @@ export function showPrettyDuration(timeFrom, timeTo, quickRanges = []) { return true; } - const fromDateMode = getDateMode(timeFrom); - const toDateMode = getDateMode(timeTo); - return fromDateMode === DATE_MODES.RELATIVE && toDateMode === DATE_MODES.NOW; + return isRelativeToNow(timeFrom, timeTo); } diff --git a/src/components/date_picker/super_date_picker/pretty_duration.test.js b/src/components/date_picker/super_date_picker/pretty_duration.test.js index af960f18f3f..c1e6560f4f4 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.test.js +++ b/src/components/date_picker/super_date_picker/pretty_duration.test.js @@ -20,15 +20,15 @@ describe('prettyDuration', () => { }); test('last', () => { - const timeFrom = 'now-1M'; + const timeFrom = 'now-16m'; const timeTo = 'now'; - expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('Last 1M'); + expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).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 1M rounded to the week'); + expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('Last 1 month rounded to the week'); }); test('from is in past', () => { diff --git a/src/components/date_picker/super_date_picker/relative_utils.js b/src/components/date_picker/super_date_picker/relative_utils.js index c77fc35de07..096d6a65243 100644 --- a/src/components/date_picker/super_date_picker/relative_utils.js +++ b/src/components/date_picker/super_date_picker/relative_utils.js @@ -4,6 +4,8 @@ import moment from 'moment'; import _ from 'lodash'; import { relativeOptions } from './relative_options'; +const ROUND_DELIMETER = '/'; + export function parseRelativeParts(value) { const matches = _.isString(value) && value.match(/now(([\-\+])([0-9]+)([smhdwMy])(\/[smhdwMy])?)?/); @@ -18,10 +20,12 @@ export function parseRelativeParts(value) { } if (count && unit) { + const isRounded = roundBy ? true : false; return { count: parseInt(count, 10), unit: operator === '+' ? `${unit}+` : unit, - round: roundBy ? true : false, + round: isRounded, + roundUnit: isRounded ? roundBy.replace(ROUND_DELIMETER, '') : undefined, }; } @@ -45,17 +49,16 @@ export function parseRelativeParts(value) { export function toRelativeStringFromParts(relativeParts) { const count = _.get(relativeParts, `count`, 0); - const round = _.get(relativeParts, `round`, false); + const isRounded = _.get(relativeParts, `round`, false); - if (count === 0 && !round) { + if (count === 0 && !isRounded) { return 'now'; } const matches = _.get(relativeParts, `unit`, 's').match(/([smhdwMy])(\+)?/); const unit = matches[1]; const operator = matches && matches[2] ? matches[2] : '-'; + const round = isRounded ? `${ROUND_DELIMETER}${unit}` : ''; - let result = `now${operator}${count}${unit}`; - result += (round ? `/${unit}` : ''); - return result; + return `now${operator}${count}${unit}${round}`; } diff --git a/src/components/date_picker/super_date_picker/relative_utils.test.js b/src/components/date_picker/super_date_picker/relative_utils.test.js index 8df1dbf074f..43fe7b048c9 100644 --- a/src/components/date_picker/super_date_picker/relative_utils.test.js +++ b/src/components/date_picker/super_date_picker/relative_utils.test.js @@ -27,7 +27,8 @@ describe('parseRelativeParts', () => { expect(out).toEqual({ count: 2, unit: 'h', - round: true + round: true, + roundUnit: 'h', }); }); @@ -36,7 +37,8 @@ describe('parseRelativeParts', () => { expect(out).toEqual({ count: 10, unit: 'm+', - round: true + round: true, + roundUnit: 'm', }); }); }); diff --git a/src/components/date_picker/super_date_picker/time_units.js b/src/components/date_picker/super_date_picker/time_units.js index ce19b67f2b4..3ad61fca541 100644 --- a/src/components/date_picker/super_date_picker/time_units.js +++ b/src/components/date_picker/super_date_picker/time_units.js @@ -8,4 +8,3 @@ export const timeUnits = { M: 'month', y: 'year' }; - From 3a4aac096d3992f603afabd20c7a733d70236026 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 7 Dec 2018 12:07:20 -0700 Subject: [PATCH 19/37] pass roundUp to formatTimeString to ensure date button display is accurate, make onRefreshChange prop optional --- .../super_date_picker/date_popover/date_popover_button.js | 2 +- .../quick_select_popover/quick_select_popover.js | 2 +- .../quick_select_popover/refresh_interval.js | 6 +++++- .../date_picker/super_date_picker/super_date_picker.js | 5 ++++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js index 83b933ae9c0..3a05d99b516 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js @@ -73,7 +73,7 @@ export class DatePopoverButton extends Component { data-test-subj={`superDatePicker${this.props.position}DatePopoverButton`} {...buttonProps} > - {formatTimeString(value, dateFormat)} + {formatTimeString(value, dateFormat, roundUp)} ); diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js index 88ceadf2d9c..9f2d44d4d37 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js @@ -103,7 +103,7 @@ QuickSelectPopover.propTypes = { applyTime: PropTypes.func.isRequired, from: PropTypes.string.isRequired, to: PropTypes.string.isRequired, - applyRefreshInterval: PropTypes.func.isRequired, + applyRefreshInterval: PropTypes.func, isPaused: PropTypes.bool.isRequired, refreshInterval: PropTypes.number.isRequired, commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRangeShape).isRequired, diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js b/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js index d665947099b..9483c9584e0 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js @@ -87,6 +87,10 @@ export class RefreshInterval extends Component { } render() { + if (!this.props.applyRefreshInterval) { + return null; + } + return ( Refresh every @@ -134,7 +138,7 @@ export class RefreshInterval extends Component { } RefreshInterval.propTypes = { - applyRefreshInterval: PropTypes.func.isRequired, + applyRefreshInterval: PropTypes.func, isPaused: PropTypes.bool.isRequired, refreshInterval: PropTypes.number.isRequired, }; diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index 94892e92b3c..ab40c48e716 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -214,7 +214,10 @@ EuiSuperDatePicker.propTypes = { * Refresh interval in milliseconds */ refreshInterval: PropTypes.number, - onRefreshChange: PropTypes.func.isRequired, + /** + * Supply onRefreshChange to show refresh interval form in quick select popover + */ + onRefreshChange: PropTypes.func, /** * 'from' and 'to' must be string as either datemath (e.g.: now, now-15m, now-15m/m) or From db09dc22f0d5b3d442c107d00df92f7184d02448 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 7 Dec 2018 12:22:53 -0700 Subject: [PATCH 20/37] index files for super_date_picker and prefix subcomponents with 'Eui' --- src/components/date_picker/_index.scss | 2 +- src/components/date_picker/index.js | 2 +- .../date_picker/super_date_picker/_index.scss | 1 + .../date_popover/absolute_tab.js | 4 ++-- .../date_popover/date_popover_button.js | 6 +++--- .../date_popover/date_popover_content.js | 14 ++++++------- .../date_popover/relative_tab.js | 4 ++-- .../date_picker/super_date_picker/index.js | 3 +++ .../quick_select_popover/commonly_used.js | 4 ++-- .../quick_select_popover/quick_select.js | 4 ++-- .../quick_select_popover.js | 20 +++++++++---------- .../quick_select_popover/recently_used.js | 6 +++--- .../quick_select_popover/refresh_interval.js | 4 ++-- .../super_date_picker/super_date_picker.js | 10 +++++----- 14 files changed, 44 insertions(+), 40 deletions(-) create mode 100644 src/components/date_picker/super_date_picker/_index.scss create mode 100644 src/components/date_picker/super_date_picker/index.js diff --git a/src/components/date_picker/_index.scss b/src/components/date_picker/_index.scss index cc5927780e8..6ae886348b2 100644 --- a/src/components/date_picker/_index.scss +++ b/src/components/date_picker/_index.scss @@ -1,4 +1,4 @@ // Uses some form mixins @import 'date_picker'; @import 'date_picker_range'; -@import 'super_date_picker/super_date_picker'; +@import 'super_date_picker/index'; diff --git a/src/components/date_picker/index.js b/src/components/date_picker/index.js index c7087098446..00f6c0b638a 100644 --- a/src/components/date_picker/index.js +++ b/src/components/date_picker/index.js @@ -8,4 +8,4 @@ export { export { EuiSuperDatePicker, -} from './super_date_picker/super_date_picker'; +} from './super_date_picker'; diff --git a/src/components/date_picker/super_date_picker/_index.scss b/src/components/date_picker/super_date_picker/_index.scss new file mode 100644 index 00000000000..6382670c4bf --- /dev/null +++ b/src/components/date_picker/super_date_picker/_index.scss @@ -0,0 +1 @@ +@import 'super_date_picker'; diff --git a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js index cabf2b4dfd9..58813254191 100644 --- a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js +++ b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js @@ -18,7 +18,7 @@ const toMoment = (value, roundUp) => { }; }; -export class AbsoluteTab extends Component { +export class EuiAbsoluteTab extends Component { constructor(props) { super(props); @@ -79,7 +79,7 @@ export class AbsoluteTab extends Component { } } -AbsoluteTab.propTypes = { +EuiAbsoluteTab.propTypes = { dateFormat: PropTypes.string.isRequired, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js index 3a05d99b516..e3dd142d427 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js @@ -5,9 +5,9 @@ import classNames from 'classnames'; import { EuiPopover } from '../../../popover'; import { formatTimeString } from '../pretty_duration'; -import { DatePopoverContent } from './date_popover_content'; +import { EuiDatePopoverContent } from './date_popover_content'; -export class DatePopoverButton extends Component { +export class EuiDatePopoverButton extends Component { static propTypes = { position: PropTypes.oneOf(['start', 'end']), isInvalid: PropTypes.bool, @@ -87,7 +87,7 @@ export class DatePopoverButton extends Component { ownFocus {...rest} > - { switch(selectedTab.id) { @@ -37,7 +37,7 @@ export function DatePopoverContent({ value, roundUp, onChange, dateFormat }) { id: DATE_MODES.ABSOLUTE, name: 'Absolute', content: ( - { const applyCommonlyUsed = () => { applyTime({ from, to }); @@ -41,7 +41,7 @@ export function CommonlyUsed({ applyTime, commonlyUsedRanges }) { ); } -CommonlyUsed.propTypes = { +EuiCommonlyUsed.propTypes = { applyTime: PropTypes.func.isRequired, commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRangeShape).isRequired, }; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js index d57e1b5d885..52d58dc56e9 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js @@ -25,7 +25,7 @@ const timeUnitsOptions = Object.keys(timeUnits).map(key => { return { value: key, text: `${timeUnits[key]}s` }; }); -export class QuickSelect extends Component { +export class EuiQuickSelect extends Component { state = { timeTense: LAST, timeValue: 15, @@ -173,7 +173,7 @@ export class QuickSelect extends Component { } } -QuickSelect.propTypes = { +EuiQuickSelect.propTypes = { applyTime: PropTypes.func.isRequired, from: PropTypes.string.isRequired, to: PropTypes.string.isRequired, diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js index 9f2d44d4d37..78fbc4c3f77 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js @@ -15,12 +15,12 @@ import { EuiPopover, } from '../../../popover'; -import { QuickSelect } from './quick_select'; -import { CommonlyUsed } from './commonly_used'; -import { RecentlyUsed } from './recently_used'; -import { RefreshInterval } from './refresh_interval'; +import { EuiQuickSelect } from './quick_select'; +import { EuiCommonlyUsed } from './commonly_used'; +import { EuiRecentlyUsed } from './recently_used'; +import { EuiRefreshInterval } from './refresh_interval'; -export class QuickSelectPopover extends Component { +export class EuiQuickSelectPopover extends Component { state = { isOpen: false, @@ -73,22 +73,22 @@ export class QuickSelectPopover extends Component { style={{ width: 400, maxWidth: '100%' }} data-test-subj="superDatePickerQuickMenu" > - - - - } endDateControl={ - Date: Fri, 7 Dec 2018 12:50:25 -0700 Subject: [PATCH 21/37] add isLoading prop to EuiSuperDatePicker --- .../views/date_picker/super_date_picker.js | 17 +- .../super_date_picker.test.js.snap | 272 ++++++++++++++++++ .../super_date_picker/super_date_picker.js | 12 +- .../super_date_picker.test.js | 33 +++ 4 files changed, 329 insertions(+), 5 deletions(-) create mode 100644 src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.js.snap create mode 100644 src/components/date_picker/super_date_picker/super_date_picker.test.js diff --git a/src-docs/src/views/date_picker/super_date_picker.js b/src-docs/src/views/date_picker/super_date_picker.js index 71458f619cd..908b8cb43ae 100644 --- a/src-docs/src/views/date_picker/super_date_picker.js +++ b/src-docs/src/views/date_picker/super_date_picker.js @@ -8,7 +8,8 @@ import { export default class extends Component { state = { - recentlyUsedRanges: [] + recentlyUsedRanges: [], + isLoading: false, } onTimeChange = ({ from, to }) => { @@ -22,8 +23,19 @@ export default class extends Component { from, to, recentlyUsedRanges: recentlyUsedRanges.length > 10 ? recentlyUsedRanges.slice(0, 9) : recentlyUsedRanges, + isLoading: true, }; - }); + }, this.startLoading); + } + + startLoading = () => { + setTimeout( + this.stopLoading, + 1000); + } + + stopLoading = () => { + this.setState({ isLoading: false }); } onRefreshChange = ({ isPaused, refreshInterval }) => { @@ -38,6 +50,7 @@ export default class extends Component { render() { return ( + + + } + > + } + iconType={false} + isCustom={true} + startDateControl={
} + > +
+ Last 15 minutes +
+ + Show dates + + + + + + + Refresh + + + +`; + +exports[`EuiSuperDatePicker isLoading 1`] = ` + + + + } + > + } + iconType={false} + isCustom={true} + startDateControl={
} + > +
+ Last 15 minutes +
+ + Show dates + + + + + + + Updating + + + +`; diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index e2c8f5f4bce..c0713139ddc 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -146,18 +146,23 @@ export class EuiSuperDatePicker extends Component { } renderUpdateButton = () => { + let buttonText = 'Refresh'; + if (this.state.hasChanged || this.props.isLoading) { + buttonText = this.props.isLoading ? 'Updating' : 'Update'; + } return ( - {this.state.hasChanged ? 'Update' : 'Refresh'} + {buttonText} ); } @@ -198,6 +203,7 @@ export class EuiSuperDatePicker extends Component { } EuiSuperDatePicker.propTypes = { + isLoading: PropTypes.bool, /** * String as either datemath (e.g.: now, now-15m, now-15m/m) or * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.sssZ' diff --git a/src/components/date_picker/super_date_picker/super_date_picker.test.js b/src/components/date_picker/super_date_picker/super_date_picker.test.js new file mode 100644 index 00000000000..22af2b5f6ba --- /dev/null +++ b/src/components/date_picker/super_date_picker/super_date_picker.test.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import { + EuiSuperDatePicker, +} from './super_date_picker'; + +const noop = () => {} + +describe('EuiSuperDatePicker', () => { + test('is rendered', () => { + const component = shallow( + + ); + + expect(component) + .toMatchSnapshot(); + }); + + test('isLoading', () => { + const component = shallow( + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); From e440d6d7b9820b6f18bdce0effcb1e2ceefca1d4 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 7 Dec 2018 12:58:52 -0700 Subject: [PATCH 22/37] add props documenation for callback functions --- .../date_picker/super_date_picker/super_date_picker.js | 4 ++++ .../date_picker/super_date_picker/super_date_picker.test.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index c0713139ddc..1a0ce6cc02e 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -214,6 +214,9 @@ EuiSuperDatePicker.propTypes = { * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.sssZ' */ to: PropTypes.string, + /** + * Callback for when the time changes. Called with { from, to } + */ onTimeChange: PropTypes.func.isRequired, isPaused: PropTypes.bool, /** @@ -221,6 +224,7 @@ EuiSuperDatePicker.propTypes = { */ refreshInterval: PropTypes.number, /** + * Callback for when the time changes. Called with { isPaused, refreshInterval } * Supply onRefreshChange to show refresh interval form in quick select popover */ onRefreshChange: PropTypes.func, diff --git a/src/components/date_picker/super_date_picker/super_date_picker.test.js b/src/components/date_picker/super_date_picker/super_date_picker.test.js index 22af2b5f6ba..5fa4f20e4ed 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.test.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.test.js @@ -5,7 +5,7 @@ import { EuiSuperDatePicker, } from './super_date_picker'; -const noop = () => {} +const noop = () => {}; describe('EuiSuperDatePicker', () => { test('is rendered', () => { From a45a277263e5d084cb6a0e66c04674c748c2184d Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 7 Dec 2018 13:19:57 -0700 Subject: [PATCH 23/37] show 'next' relative ranges as pretty duration --- .../super_date_picker/pretty_duration.js | 25 ++++++++++++------- .../super_date_picker/pretty_duration.test.js | 12 ++++++++- .../super_date_picker/super_date_picker.js | 4 +-- .../super_date_picker/time_units.js | 10 ++++++++ 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/components/date_picker/super_date_picker/pretty_duration.js b/src/components/date_picker/super_date_picker/pretty_duration.js index 8ac5c060436..0a0564ef657 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.js +++ b/src/components/date_picker/super_date_picker/pretty_duration.js @@ -1,7 +1,7 @@ import dateMath from '@elastic/datemath'; import moment from 'moment'; -import { timeUnits } from './time_units'; +import { timeUnits, timeUnitsPlural } from './time_units'; import { getDateMode, DATE_MODES } from './date_modes'; import { parseRelativeParts } from './relative_utils'; @@ -16,7 +16,9 @@ function cantLookup(timeFrom, timeTo, dateFormat) { function isRelativeToNow(timeFrom, timeTo) { const fromDateMode = getDateMode(timeFrom); const toDateMode = getDateMode(timeTo); - return fromDateMode === DATE_MODES.RELATIVE && toDateMode === DATE_MODES.NOW; + 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, dateFormat, roundUp = false) { @@ -46,15 +48,20 @@ export function prettyDuration(timeFrom, timeTo, quickRanges = [], dateFormat) { } if (isRelativeToNow(timeFrom, timeTo)) { - const relativeParts = parseRelativeParts(timeFrom); - let countTimeUnit = timeUnits[relativeParts.unit.substring(0, 1)]; - if (relativeParts.count > 1) { - countTimeUnit += 's'; + let timeTense; + let relativeParts; + if (getDateMode(timeTo) === DATE_MODES.NOW) { + timeTense = 'Last'; + relativeParts = parseRelativeParts(timeFrom); + } else { + timeTense = 'Next'; + relativeParts = parseRelativeParts(timeTo); } - let text = `Last ${relativeParts.count} ${countTimeUnit}`; + const countTimeUnit = relativeParts.unit.substring(0, 1); + const countTimeUnitFullName = relativeParts.count > 1 ? timeUnitsPlural[countTimeUnit] : timeUnits[countTimeUnit]; + let text = `${timeTense} ${relativeParts.count} ${countTimeUnitFullName}`; if (relativeParts.round) { - const roundTimeUnit = timeUnits[relativeParts.roundUnit] ? timeUnits[relativeParts.roundUnit] : relativeParts.roundUnit; - text += ` rounded to the ${roundTimeUnit}`; + text += ` rounded to the ${timeUnits[relativeParts.roundUnit]}`; } return text; } diff --git a/src/components/date_picker/super_date_picker/pretty_duration.test.js b/src/components/date_picker/super_date_picker/pretty_duration.test.js index c1e6560f4f4..af80a8e2734 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.test.js +++ b/src/components/date_picker/super_date_picker/pretty_duration.test.js @@ -31,6 +31,12 @@ describe('prettyDuration', () => { expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).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'); + }); + test('from is in past', () => { const timeFrom = 'now-17m'; const timeTo = 'now-15m'; @@ -50,10 +56,14 @@ describe('showPrettyDuration', () => { expect(showPrettyDuration('now-15m', 'now', quickRanges)).toBe(true); }); - test('should show pretty duration for relative to now', () => { + test('should show pretty duration for last', () => { expect(showPrettyDuration('now-17m', 'now', quickRanges)).toBe(true); }); + test('should show pretty duration for next', () => { + expect(showPrettyDuration('now', 'now+17m', quickRanges)).toBe(true); + }); + test('should not show pretty duration for relative to relative', () => { expect(showPrettyDuration('now-17m', 'now-3m', quickRanges)).toBe(false); }); diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index 1a0ce6cc02e..78643a361fb 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -224,8 +224,8 @@ EuiSuperDatePicker.propTypes = { */ refreshInterval: PropTypes.number, /** - * Callback for when the time changes. Called with { isPaused, refreshInterval } - * Supply onRefreshChange to show refresh interval form in quick select popover + * Callback for when the refresh interval changes. Called with { isPaused, refreshInterval } + * Supply onRefreshChange to show refresh interval inputs in quick select popover */ onRefreshChange: PropTypes.func, diff --git a/src/components/date_picker/super_date_picker/time_units.js b/src/components/date_picker/super_date_picker/time_units.js index 3ad61fca541..73766803588 100644 --- a/src/components/date_picker/super_date_picker/time_units.js +++ b/src/components/date_picker/super_date_picker/time_units.js @@ -8,3 +8,13 @@ export const timeUnits = { M: 'month', y: 'year' }; + +export const timeUnitsPlural = { + s: 'seconds', + m: 'minutes', + h: 'hours', + d: 'days', + w: 'weeks', + M: 'months', + y: 'years' +}; From 2ef51714221191d9a8eef2320d1245c2cf780fc1 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 7 Dec 2018 14:11:02 -0700 Subject: [PATCH 24/37] rename from and to -> start and end --- .../views/date_picker/super_date_picker.js | 14 +-- .../super_date_picker.test.js.snap | 72 +++++++------- .../super_date_picker/pretty_duration.js | 4 +- .../super_date_picker/pretty_duration.test.js | 4 +- .../quick_select_popover/commonly_used.js | 4 +- .../quick_select_popover/quick_select.js | 24 ++--- .../quick_select_popover.js | 14 +-- .../quick_select_popover/recently_used.js | 8 +- .../super_date_picker/super_date_picker.js | 94 +++++++++---------- .../date_picker/super_date_picker/types.js | 8 +- 10 files changed, 123 insertions(+), 123 deletions(-) diff --git a/src-docs/src/views/date_picker/super_date_picker.js b/src-docs/src/views/date_picker/super_date_picker.js index 908b8cb43ae..47c0347522a 100644 --- a/src-docs/src/views/date_picker/super_date_picker.js +++ b/src-docs/src/views/date_picker/super_date_picker.js @@ -12,16 +12,16 @@ export default class extends Component { isLoading: false, } - onTimeChange = ({ from, to }) => { + onTimeChange = ({ start, end }) => { this.setState((prevState) => { const recentlyUsedRanges = prevState.recentlyUsedRanges.filter(recentlyUsedRange => { - const isDuplicate = recentlyUsedRange.from === from && recentlyUsedRange.to === to; + const isDuplicate = recentlyUsedRange.start === start && recentlyUsedRange.end === end; return !isDuplicate; }); - recentlyUsedRanges.unshift({ from, to }); + recentlyUsedRanges.unshift({ start, end }); return { - from, - to, + start, + end, recentlyUsedRanges: recentlyUsedRanges.length > 10 ? recentlyUsedRanges.slice(0, 9) : recentlyUsedRanges, isLoading: true, }; @@ -51,8 +51,8 @@ export default class extends Component { return ( } > @@ -164,53 +164,53 @@ exports[`EuiSuperDatePicker isLoading 1`] = ` commonlyUsedRanges={ Array [ Object { - "from": "now/d", + "end": "now/d", "label": "Today", - "to": "now/d", + "start": "now/d", }, Object { - "from": "now-1d/d", + "end": "now-1d/d", "label": "Yesterday", - "to": "now-1d/d", + "start": "now-1d/d", }, Object { - "from": "now/w", + "end": "now/w", "label": "This week", - "to": "now/w", + "start": "now/w", }, Object { - "from": "now/w", + "end": "now", "label": "Week to date", - "to": "now", + "start": "now/w", }, Object { - "from": "now/M", + "end": "now/M", "label": "This month", - "to": "now/M", + "start": "now/M", }, Object { - "from": "now/M", + "end": "now", "label": "Month to date", - "to": "now", + "start": "now/M", }, Object { - "from": "now/y", + "end": "now/y", "label": "This year", - "to": "now/y", + "start": "now/y", }, Object { - "from": "now/y", + "end": "now", "label": "Year to date", - "to": "now", + "start": "now/y", }, ] } dateFormat="MMM D, YYYY @ HH:mm:ss.SSS" - from="now-15m" + end="now" isPaused={true} recentlyUsedRanges={Array []} refreshInterval={0} - to="now" + start="now-15m" /> } > diff --git a/src/components/date_picker/super_date_picker/pretty_duration.js b/src/components/date_picker/super_date_picker/pretty_duration.js index 0a0564ef657..ed357c1841b 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.js +++ b/src/components/date_picker/super_date_picker/pretty_duration.js @@ -40,7 +40,7 @@ export function formatTimeString(timeString, dateFormat, roundUp = false) { } export function prettyDuration(timeFrom, timeTo, quickRanges = [], dateFormat) { - const matchingQuickRange = quickRanges.find(({ from: quickFrom, to: quickTo }) => { + const matchingQuickRange = quickRanges.find(({ start: quickFrom, end: quickTo }) => { return timeFrom === quickFrom && timeTo === quickTo; }); if (matchingQuickRange) { @@ -70,7 +70,7 @@ export function prettyDuration(timeFrom, timeTo, quickRanges = [], dateFormat) { } export function showPrettyDuration(timeFrom, timeTo, quickRanges = []) { - const matchingQuickRange = quickRanges.find(({ from: quickFrom, to: quickTo }) => { + const matchingQuickRange = quickRanges.find(({ start: quickFrom, end: quickTo }) => { return timeFrom === quickFrom && timeTo === quickTo; }); if (matchingQuickRange) { diff --git a/src/components/date_picker/super_date_picker/pretty_duration.test.js b/src/components/date_picker/super_date_picker/pretty_duration.test.js index af80a8e2734..5f240a6e8bf 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.test.js +++ b/src/components/date_picker/super_date_picker/pretty_duration.test.js @@ -6,8 +6,8 @@ moment.tz.setDefault('UTC'); const dateFormat = 'MMMM Do YYYY, HH:mm:ss.SSS'; const quickRanges = [ { - from: 'now-15m', - to: 'now', + start: 'now-15m', + end: 'now', label: 'quick range 15 minutes custom display', } ]; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js b/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js index 5e6aa8c464d..b7a1822fa88 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js @@ -11,9 +11,9 @@ import { EuiText } from '../../../text'; import { EuiHorizontalRule } from '../../../horizontal_rule'; export function EuiCommonlyUsed({ applyTime, commonlyUsedRanges }) { - const links = commonlyUsedRanges.map(({ from, to, label }) => { + const links = commonlyUsedRanges.map(({ start, end, label }) => { const applyCommonlyUsed = () => { - applyTime({ from, to }); + applyTime({ start, end }); }; return ( diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js index 52d58dc56e9..ff291604e7c 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js @@ -60,22 +60,22 @@ export class EuiQuickSelect extends Component { if (timeTense === NEXT) { this.props.applyTime({ - from: 'now', - to: `now+${timeValue}${timeUnits}` + start: 'now', + end: `now+${timeValue}${timeUnits}` }); return; } this.props.applyTime({ - from: `now-${timeValue}${timeUnits}`, - to: 'now' + start: `now-${timeValue}${timeUnits}`, + end: 'now' }); } getBounds = () => { return { - min: dateMath.parse(this.props.from), - max: dateMath.parse(this.props.to, { roundUp: true }), + min: dateMath.parse(this.props.start), + max: dateMath.parse(this.props.end, { roundUp: true }), }; } @@ -83,8 +83,8 @@ export class EuiQuickSelect extends Component { const { min, max } = this.getBounds(); const diff = max.diff(min); this.props.applyTime({ - from: moment(max).add(1, 'ms').toISOString(), - to: moment(max).add(diff + 1, 'ms').toISOString(), + start: moment(max).add(1, 'ms').toISOString(), + end: moment(max).add(diff + 1, 'ms').toISOString(), }); } @@ -92,8 +92,8 @@ export class EuiQuickSelect extends Component { const { min, max } = this.getBounds(); const diff = max.diff(min); this.props.applyTime({ - from: moment(min).subtract(diff + 1, 'ms').toISOString(), - to: moment(min).subtract(1, 'ms').toISOString(), + start: moment(min).subtract(diff + 1, 'ms').toISOString(), + end: moment(min).subtract(1, 'ms').toISOString(), }); } @@ -175,6 +175,6 @@ export class EuiQuickSelect extends Component { EuiQuickSelect.propTypes = { applyTime: PropTypes.func.isRequired, - from: PropTypes.string.isRequired, - to: PropTypes.string.isRequired, + start: PropTypes.string.isRequired, + end: PropTypes.string.isRequired, }; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js index 78fbc4c3f77..b0500d122a7 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js @@ -36,10 +36,10 @@ export class EuiQuickSelectPopover extends Component { })); } - applyTime = ({ from, to }) => { + applyTime = ({ start, end }) => { this.props.applyTime({ - from, - to, + start, + end, }); this.closePopover(); } @@ -75,8 +75,8 @@ export class EuiQuickSelectPopover extends Component { > { + const links = recentlyUsedRanges.map(({ start, end }) => { const applyRecentlyUsed = () => { - applyTime({ from, to }); + applyTime({ start, end }); }; return ( - + - {prettyDuration(from, to, commonlyUsedRanges, dateFormat)} + {prettyDuration(start, end, commonlyUsedRanges, dateFormat)} ); diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index 78643a361fb..7ce4ec8f25b 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -20,63 +20,63 @@ export class EuiSuperDatePicker extends Component { super(props); const { - from, - to, + start, + end, commonlyUsedRanges } = this.props; this.state = { - from, - to, + start, + end, isInvalid: false, hasChanged: false, - showPrettyDuration: showPrettyDuration(from, to, commonlyUsedRanges), + showPrettyDuration: showPrettyDuration(start, end, commonlyUsedRanges), }; } static getDerivedStateFromProps = (nextProps) => { const { - from, - to, + start, + end, commonlyUsedRanges } = nextProps; return { - from, - to, + start, + end, isInvalid: false, hasChanged: false, - showPrettyDuration: showPrettyDuration(from, to, commonlyUsedRanges), + showPrettyDuration: showPrettyDuration(start, end, commonlyUsedRanges), }; } - setTime = ({ from, to }) => { - const fromMoment = dateMath.parse(from); - const toMoment = dateMath.parse(to, { roundUp: true }); - const isInvalid = (from === 'now' && to === 'now') || fromMoment.isAfter(toMoment); + setTime = ({ start, end }) => { + const startMoment = dateMath.parse(start); + const endMoment = dateMath.parse(end, { roundUp: true }); + const isInvalid = (start === 'now' && end === 'now') || startMoment.isAfter(endMoment); this.setState({ - from, - to, + start, + end, isInvalid, hasChanged: true, }); } - setFrom = (from) => { - this.setTime({ from, to: this.state.to }); + setStart = (start) => { + this.setTime({ start, end: this.state.end }); } - setTo = (to) => { - this.setTime({ from: this.state.from, to }); + setEnd = (end) => { + this.setTime({ start: this.state.start, end }); } applyTime = () => { - this.props.onTimeChange({ from: this.state.from, to: this.state.to }); + this.props.onTimeChange({ start: this.state.start, end: this.state.end }); } - applyQuickTime = ({ from, to }) => { - this.props.onTimeChange({ from, to }); + applyQuickTime = ({ start, end }) => { + this.props.onTimeChange({ start, end }); } hidePrettyDuration = () => { @@ -85,8 +85,8 @@ export class EuiSuperDatePicker extends Component { renderDatePickerRange = () => { const { - from, - to, + start, + end, hasChanged, isInvalid, } = this.state; @@ -101,7 +101,7 @@ export class EuiSuperDatePicker extends Component { endDateControl={
} >
- {prettyDuration(from, to, this.props.commonlyUsedRanges, this.props.dateFormat)} + {prettyDuration(start, end, this.props.commonlyUsedRanges, this.props.dateFormat)}
} @@ -135,8 +135,8 @@ export class EuiSuperDatePicker extends Component { position="end" needsUpdating={hasChanged} isInvalid={isInvalid} - onChange={this.setTo} - value={to} + onChange={this.setEnd} + value={end} dateFormat={this.props.dateFormat} roundUp /> @@ -171,8 +171,8 @@ export class EuiSuperDatePicker extends Component { const quickSelect = ( Date: Fri, 7 Dec 2018 14:31:03 -0700 Subject: [PATCH 25/37] store previous quick select state --- .../__snapshots__/quick_select.test.js.snap | 433 ++++++++++++++++++ .../quick_select_popover/quick_select.js | 26 +- .../quick_select_popover/quick_select.test.js | 41 ++ .../quick_select_popover.js | 6 +- 4 files changed, 498 insertions(+), 8 deletions(-) create mode 100644 src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select.test.js.snap create mode 100644 src/components/date_picker/super_date_picker/quick_select_popover/quick_select.test.js diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select.test.js.snap b/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select.test.js.snap new file mode 100644 index 00000000000..95e30574a8e --- /dev/null +++ b/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select.test.js.snap @@ -0,0 +1,433 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiSuperDatePicker is rendered 1`] = ` + + + + + + Quick select + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Apply + + + + + + +`; + +exports[`EuiSuperDatePicker prevQuickSelect 1`] = ` + + + + + + Quick select + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Apply + + + + + + +`; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js index ff291604e7c..f1957bee2a9 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js @@ -26,10 +26,15 @@ const timeUnitsOptions = Object.keys(timeUnits).map(key => { }); export class EuiQuickSelect extends Component { - state = { - timeTense: LAST, - timeValue: 15, - timeUnits: 'm', + constructor(props) { + super(props); + + const { timeTense, timeValue, timeUnits } = this.props.prevQuickSelect; + this.state = { + timeTense: timeTense ? timeTense : LAST, + timeValue: timeValue ? timeValue : 15, + timeUnits: timeUnits ? timeUnits : 'm', + }; } onTimeTenseChange = (evt) => { @@ -61,14 +66,16 @@ export class EuiQuickSelect extends Component { if (timeTense === NEXT) { this.props.applyTime({ start: 'now', - end: `now+${timeValue}${timeUnits}` + end: `now+${timeValue}${timeUnits}`, + quickSelect: { ...this.state }, }); return; } this.props.applyTime({ start: `now-${timeValue}${timeUnits}`, - end: 'now' + end: 'now', + quickSelect: { ...this.state }, }); } @@ -160,7 +167,7 @@ export class EuiQuickSelect extends Component { size="s" onClick={this.applyQuickSelect} style={{ minWidth: 0 }} - disabled={this.state.timeValue === ''} + disabled={this.state.timeValue === '' || this.state.timeValue <= 0} > Apply @@ -177,4 +184,9 @@ EuiQuickSelect.propTypes = { applyTime: PropTypes.func.isRequired, start: PropTypes.string.isRequired, end: PropTypes.string.isRequired, + prevQuickSelect: PropTypes.object, +}; + +EuiQuickSelect.defaultProps = { + prevQuickSelect: {}, }; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.test.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.test.js new file mode 100644 index 00000000000..49733a1b676 --- /dev/null +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.test.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import { + EuiQuickSelect, +} from './quick_select'; + +const noop = () => {}; +const defaultProps = { + applyTime: noop, + start: 'now-15m', + end: 'now', +}; +describe('EuiSuperDatePicker', () => { + test('is rendered', () => { + const component = shallow( + + ); + + expect(component) + .toMatchSnapshot(); + }); + + test('prevQuickSelect', () => { + const component = shallow( + + ); + + expect(component) + .toMatchSnapshot(); + }); +}); diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js index b0500d122a7..6d45ec93001 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js @@ -36,11 +36,14 @@ export class EuiQuickSelectPopover extends Component { })); } - applyTime = ({ start, end }) => { + applyTime = ({ start, end, quickSelect }) => { this.props.applyTime({ start, end, }); + if (quickSelect) { + this.setState({ prevQuickSelect: quickSelect }); + } this.closePopover(); } @@ -77,6 +80,7 @@ export class EuiQuickSelectPopover extends Component { applyTime={this.applyTime} start={this.props.start} end={this.props.end} + prevQuickSelect={this.state.prevQuickSelect} /> Date: Sun, 9 Dec 2018 09:44:28 -0700 Subject: [PATCH 26/37] snapshot updates --- .../super_date_picker.test.js.snap | 80 +++++++++++-------- .../date_popover/date_popover_button.js | 2 +- .../super_date_picker/super_date_picker.js | 30 ++++--- 3 files changed, 64 insertions(+), 48 deletions(-) diff --git a/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.js.snap b/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.js.snap index 6b2aeb6591d..cf18215a325 100644 --- a/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.js.snap +++ b/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.js.snap @@ -113,24 +113,29 @@ exports[`EuiSuperDatePicker is rendered 1`] = ` component="div" grow={false} > - - Refresh - + + Refresh + + `; @@ -248,25 +253,30 @@ exports[`EuiSuperDatePicker isLoading 1`] = ` component="div" grow={false} > - - Updating - + + Updating + + `; diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js index e3dd142d427..2463b8343b5 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js @@ -82,7 +82,7 @@ export class EuiDatePopoverButton extends Component { button={button} isOpen={this.state.isOpen} closePopover={this.closePopover} - anchorPosition="downRight" + anchorPosition={this.props.position === 'start' ? 'downLeft' : 'downRight'} panelPaddingSize="none" ownFocus {...rest} diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index 7ce4ec8f25b..0d29fc507aa 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -13,6 +13,7 @@ import { EuiDatePickerRange } from '../date_picker_range'; import { EuiFormControlLayout } from '../../form'; import { EuiButton, EuiButtonEmpty } from '../../button'; import { EuiFlexGroup, EuiFlexItem } from '../../flex'; +import { EuiToolTip } from '../../tool_tip'; export class EuiSuperDatePicker extends Component { @@ -151,19 +152,24 @@ export class EuiSuperDatePicker extends Component { buttonText = this.props.isLoading ? 'Updating' : 'Update'; } return ( - - {buttonText} - + + {buttonText} + + ); } From 0b447b4ea8a0638df8dce275e50d8cad92fdb378 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Sun, 9 Dec 2018 09:48:49 -0700 Subject: [PATCH 27/37] keep quick select popover open on next / previous time window arrow --- .../super_date_picker/quick_select_popover/quick_select.js | 2 ++ .../quick_select_popover/quick_select_popover.js | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js index f1957bee2a9..3d06c1dfa9b 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js @@ -92,6 +92,7 @@ export class EuiQuickSelect extends Component { this.props.applyTime({ start: moment(max).add(1, 'ms').toISOString(), end: moment(max).add(diff + 1, 'ms').toISOString(), + keepPopoverOpen: true, }); } @@ -101,6 +102,7 @@ export class EuiQuickSelect extends Component { this.props.applyTime({ start: moment(min).subtract(diff + 1, 'ms').toISOString(), end: moment(min).subtract(1, 'ms').toISOString(), + keepPopoverOpen: true, }); } diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js index 6d45ec93001..9c0fc7234fb 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js @@ -36,7 +36,7 @@ export class EuiQuickSelectPopover extends Component { })); } - applyTime = ({ start, end, quickSelect }) => { + applyTime = ({ start, end, quickSelect, keepPopoverOpen = false }) => { this.props.applyTime({ start, end, @@ -44,7 +44,9 @@ export class EuiQuickSelectPopover extends Component { if (quickSelect) { this.setState({ prevQuickSelect: quickSelect }); } - this.closePopover(); + if (!keepPopoverOpen) { + this.closePopover(); + } } render() { From b4a0f21ee0bd171c2b9ddbca020eebdd0da08a37 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 10 Dec 2018 12:17:51 -0700 Subject: [PATCH 28/37] make pretty duration clickable, add paragraph to example --- .../views/date_picker/date_picker_example.js | 6 ++- .../super_date_picker.test.js.snap | 40 +++++++++++++++++-- .../super_date_picker/super_date_picker.js | 9 ++++- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src-docs/src/views/date_picker/date_picker_example.js b/src-docs/src/views/date_picker/date_picker_example.js index 1dfa0787cd7..b326f755eaa 100644 --- a/src-docs/src/views/date_picker/date_picker_example.js +++ b/src-docs/src/views/date_picker/date_picker_example.js @@ -276,7 +276,11 @@ export const DatePickerExample = { }], text: (
- Super date picker +

+ start and end date times are passed as strings + in either datemath format (e.g.: now, now-15m, now-15m/m) + or as absolute date in the format YYYY-MM-DDTHH:mm:ss.sssZ +

), demo: , diff --git a/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.js.snap b/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.js.snap index cf18215a325..5147c25d067 100644 --- a/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.js.snap +++ b/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.js.snap @@ -86,11 +86,27 @@ exports[`EuiSuperDatePicker is rendered 1`] = ` isCustom={true} startDateControl={
} > -
Last 15 minutes -
+ } > -
Last 15 minutes -
+
} endDateControl={
} > -
+ {prettyDuration(start, end, this.props.commonlyUsedRanges, this.props.dateFormat)} -
+ Date: Mon, 10 Dec 2018 13:56:35 -0700 Subject: [PATCH 29/37] show tooltip when time changes --- .../super_date_picker/super_date_picker.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index 287213362e2..aa0f5e18fdc 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -51,17 +51,35 @@ export class EuiSuperDatePicker extends Component { }; } + setTootipRef = node => (this.tooltip = node); + + showTooltip = () => this.tooltip.showToolTip(); + hideTooltip = () => this.tooltip.hideToolTip(); + setTime = ({ start, end }) => { const startMoment = dateMath.parse(start); const endMoment = dateMath.parse(end, { roundUp: true }); const isInvalid = (start === 'now' && end === 'now') || startMoment.isAfter(endMoment); + if (this.tooltipTimeout) { + clearTimeout(this.tooltipTimeout); + this.hideTooltip(); + this.tooltipTimeout === null; + } + this.setState({ start, end, isInvalid, hasChanged: true, }); + + if (!isInvalid) { + this.showTooltip(); + this.tooltipTimeout = setTimeout(() => { + this.hideTooltip(); + }, 2000); + } } setStart = (start) => { @@ -158,6 +176,7 @@ export class EuiSuperDatePicker extends Component { } return ( From 0a0c770dfba7787f9e3769d286efa8a11a88b3d9 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 11 Dec 2018 08:47:50 -0700 Subject: [PATCH 30/37] allow setting relative unit when value is zero --- .../super_date_picker/date_popover/relative_tab.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/date_picker/super_date_picker/date_popover/relative_tab.js b/src/components/date_picker/super_date_picker/date_popover/relative_tab.js index 29231beee03..085f41e5a27 100644 --- a/src/components/date_picker/super_date_picker/date_popover/relative_tab.js +++ b/src/components/date_picker/super_date_picker/date_popover/relative_tab.js @@ -28,8 +28,14 @@ export class EuiRelativeTab extends Component { } static getDerivedStateFromProps = (nextProps) => { + const relativeParts = parseRelativeParts(nextProps.value); + // parseRelativeParts will always return unit of 's' when the count is zero + // Do not override state.unit when count is zero to allow users to set the unit when count is zero + if (relativeParts.count === 0) { + delete relativeParts.unit; + } return { - ...parseRelativeParts(nextProps.value), + ...relativeParts, }; } From 7f74ef757ce252e4000e5d292ff9c194b5dd7d47 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 13 Dec 2018 08:39:54 -0700 Subject: [PATCH 31/37] updates from chandlerprall review --- .../super_date_picker/date_popover/absolute_tab.js | 13 +++---------- .../date_popover/date_popover_button.js | 2 +- ...ommonly_used.js => commonly_used_time_ranges.js} | 4 ++-- .../quick_select_popover/quick_select_popover.js | 4 ++-- .../quick_select_popover/refresh_interval.js | 9 +-------- .../super_date_picker/relative_options.js | 9 +++++++++ .../date_picker/super_date_picker/relative_utils.js | 10 ++++------ .../super_date_picker/super_date_picker.js | 2 +- 8 files changed, 23 insertions(+), 30 deletions(-) rename src/components/date_picker/super_date_picker/quick_select_popover/{commonly_used.js => commonly_used_time_ranges.js} (91%) diff --git a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js index 58813254191..929bdd72bc8 100644 --- a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js +++ b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js @@ -5,11 +5,11 @@ import moment from 'moment'; import dateMath from '@elastic/datemath'; -const INPUT_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSS'; - import { EuiDatePicker } from '../../date_picker'; import { EuiFormRow, EuiFieldText } from '../../../form'; +const INPUT_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSS'; + const toMoment = (value, roundUp) => { const valueAsMoment = dateMath.parse(value, { roundUp }); return { @@ -20,14 +20,7 @@ const toMoment = (value, roundUp) => { export class EuiAbsoluteTab extends Component { - constructor(props) { - super(props); - - this.state = { - ...toMoment(this.props.value, this.props.roundUp), - isTextInvalid: false, - }; - } + state = {} static getDerivedStateFromProps = (nextProps) => { return { diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js index 2463b8343b5..6619aa81333 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js @@ -13,7 +13,7 @@ export class EuiDatePopoverButton extends Component { isInvalid: PropTypes.bool, needsUpdating: PropTypes.bool, value: PropTypes.string.isRequired, - onChange: PropTypes.func, + onChange: PropTypes.func.isRequired, dateFormat: PropTypes.string.isRequired, roundUp: PropTypes.bool, } diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js b/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used_time_ranges.js similarity index 91% rename from src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js rename to src/components/date_picker/super_date_picker/quick_select_popover/commonly_used_time_ranges.js index b7a1822fa88..a80774719e4 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used_time_ranges.js @@ -10,7 +10,7 @@ import { EuiLink } from '../../../link'; import { EuiText } from '../../../text'; import { EuiHorizontalRule } from '../../../horizontal_rule'; -export function EuiCommonlyUsed({ applyTime, commonlyUsedRanges }) { +export function EuiCommonlyUsedTimeRanges({ applyTime, commonlyUsedRanges }) { const links = commonlyUsedRanges.map(({ start, end, label }) => { const applyCommonlyUsed = () => { applyTime({ start, end }); @@ -41,7 +41,7 @@ export function EuiCommonlyUsed({ applyTime, commonlyUsedRanges }) { ); } -EuiCommonlyUsed.propTypes = { +EuiCommonlyUsedTimeRanges.propTypes = { applyTime: PropTypes.func.isRequired, commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRangeShape).isRequired, }; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js index 9c0fc7234fb..889f5ba3f34 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js @@ -16,7 +16,7 @@ import { } from '../../../popover'; import { EuiQuickSelect } from './quick_select'; -import { EuiCommonlyUsed } from './commonly_used'; +import { EuiCommonlyUsedTimeRanges } from './commonly_used_time_ranges'; import { EuiRecentlyUsed } from './recently_used'; import { EuiRefreshInterval } from './refresh_interval'; @@ -84,7 +84,7 @@ export class EuiQuickSelectPopover extends Component { end={this.props.end} prevQuickSelect={this.state.prevQuickSelect} /> - diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js b/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js index 096f1c0f881..8c0ecc1d1d1 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js @@ -34,15 +34,8 @@ function convertMilliseconds(milliseconds) { } export class EuiRefreshInterval extends Component { - constructor(props) { - super(props); - const { value, units } = convertMilliseconds(this.props.refreshInterval); - this.state = { - value, - units, - }; - } + state = {} static getDerivedStateFromProps = (nextProps) => { const { value, units } = convertMilliseconds(nextProps.refreshInterval); diff --git a/src/components/date_picker/super_date_picker/relative_options.js b/src/components/date_picker/super_date_picker/relative_options.js index 43045a13a03..677191356cf 100644 --- a/src/components/date_picker/super_date_picker/relative_options.js +++ b/src/components/date_picker/super_date_picker/relative_options.js @@ -16,3 +16,12 @@ export const relativeOptions = [ { text: 'Months from now', value: 'M+' }, { text: 'Years from now', value: 'y+' }, ]; + +export const relativeUnitsFromLargestToSmallest = relativeOptions + .filter(({ value }) => { + return !value.includes('+'); + }) + .map(({ value }) => { + return value; + }) + .reverse(); diff --git a/src/components/date_picker/super_date_picker/relative_utils.js b/src/components/date_picker/super_date_picker/relative_utils.js index 096d6a65243..4732080fe1b 100644 --- a/src/components/date_picker/super_date_picker/relative_utils.js +++ b/src/components/date_picker/super_date_picker/relative_utils.js @@ -2,7 +2,7 @@ import dateMath from '@elastic/datemath'; import moment from 'moment'; import _ from 'lodash'; -import { relativeOptions } from './relative_options'; +import { relativeUnitsFromLargestToSmallest } from './relative_options'; const ROUND_DELIMETER = '/'; @@ -31,15 +31,13 @@ export function parseRelativeParts(value) { const results = { count: 0, unit: 's', round: false }; const duration = moment.duration(moment().diff(dateMath.parse(value))); - const units = _.pluck(_.clone(relativeOptions).reverse(), 'value') - .filter(s => /^[smhdwMy]$/.test(s)); let unitOp = ''; - for (let i = 0; i < units.length; i++) { - const as = duration.as(units[i]); + for (let i = 0; i < relativeUnitsFromLargestToSmallest.length; i++) { + const as = duration.as(relativeUnitsFromLargestToSmallest[i]); if (as < 0) unitOp = '+'; if (Math.abs(as) > 1) { results.count = Math.round(Math.abs(as)); - results.unit = units[i] + unitOp; + results.unit = relativeUnitsFromLargestToSmallest[i] + unitOp; results.round = false; break; } diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index aa0f5e18fdc..e331f1a7483 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -64,7 +64,7 @@ export class EuiSuperDatePicker extends Component { if (this.tooltipTimeout) { clearTimeout(this.tooltipTimeout); this.hideTooltip(); - this.tooltipTimeout === null; + this.tooltipTimeout = null; } this.setState({ From 759ecda31baeef1e9f9e0bd43abccf9e9f861581 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 13 Dec 2018 09:50:35 -0700 Subject: [PATCH 32/37] remove moment-timezone dependency, not needed --- package.json | 1 - .../super_date_picker/pretty_duration.test.js | 9 --------- 2 files changed, 10 deletions(-) diff --git a/package.json b/package.json index 1a4f458b73a..1c4ba11e774 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,6 @@ "markdown-it": "8.4.1", "mocha": "^5.0.4", "moment": "^2.20.1", - "moment-timezone": "^0.5.14", "node-sass": "^4.9.3", "nodegit": "^0.23.0", "npm-run": "^4.1.2", diff --git a/src/components/date_picker/super_date_picker/pretty_duration.test.js b/src/components/date_picker/super_date_picker/pretty_duration.test.js index 5f240a6e8bf..3f0124eea17 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.test.js +++ b/src/components/date_picker/super_date_picker/pretty_duration.test.js @@ -1,8 +1,6 @@ -import moment from 'moment-timezone'; import { prettyDuration, showPrettyDuration } from './pretty_duration'; -moment.tz.setDefault('UTC'); const dateFormat = 'MMMM Do YYYY, HH:mm:ss.SSS'; const quickRanges = [ { @@ -42,13 +40,6 @@ describe('prettyDuration', () => { const timeTo = 'now-15m'; expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('~ 17 minutes ago to ~ 15 minutes ago'); }); - - // TODO figure out timezone to get this working - //test('absolute dates', () => { - // const timeFrom = '2018-01-17T18:57:57.149Z'; - // const timeTo = '2018-01-17T20:00:00.000Z'; - // expect(prettyDuration(timeFrom, timeTo, quickRanges, dateFormat)).toBe('January 17th 2018, 18:57:57.149 to January 17th 2018, 20:00:00.000'); - //}); }); describe('showPrettyDuration', () => { From b6bfe054e597d55776db77e87ef4aa7f491826f5 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 14 Dec 2018 14:29:50 -0700 Subject: [PATCH 33/37] remove getDerivedStateFromProps from EuiSuperDatePicker, EuiAbsoluteTab, EuiRefreshInterval, and EuiRelativeTab --- .../date_popover/absolute_tab.js | 35 ++++++++++--------- .../date_popover/relative_tab.js | 12 ------- .../date_picker/super_date_picker/index.js | 8 +++-- .../super_date_picker/pretty_duration.js | 2 +- .../quick_select_popover/refresh_interval.js | 8 ++--- .../super_date_picker/super_date_picker.js | 16 --------- .../wrapped_super_date_picker.js | 20 +++++++++++ 7 files changed, 48 insertions(+), 53 deletions(-) create mode 100644 src/components/date_picker/super_date_picker/wrapped_super_date_picker.js diff --git a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js index 929bdd72bc8..cd3c78fe4d4 100644 --- a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js +++ b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js @@ -10,39 +10,40 @@ import { EuiFormRow, EuiFieldText } from '../../../form'; const INPUT_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSS'; -const toMoment = (value, roundUp) => { - const valueAsMoment = dateMath.parse(value, { roundUp }); - return { - valueAsMoment, - textInputValue: valueAsMoment.format(INPUT_DATE_FORMAT) - }; -}; - export class EuiAbsoluteTab extends Component { - state = {} + constructor(props) { + super(props); - static getDerivedStateFromProps = (nextProps) => { - return { - ...toMoment(nextProps.value, nextProps.roundUp), + const valueAsMoment = dateMath.parse(props.value, { roundUp: props.roundUp }); + this.state = { + valueAsMoment, + textInputValue: valueAsMoment.format(INPUT_DATE_FORMAT), isTextInvalid: false, }; } handleChange = (date) => { this.props.onChange(date.toISOString()); + this.setState({ + valueAsMoment: date, + textInputValue: date.format(INPUT_DATE_FORMAT), + isTextInvalid: false, + }); } handleTextChange = (evt) => { const date = moment(evt.target.value, INPUT_DATE_FORMAT, true); + const updatedState = { + textInputValue: evt.target.value, + isTextInvalid: !date.isValid() + }; if (date.isValid()) { this.props.onChange(date.toISOString()); + updatedState.valueAsMoment = date; } - this.setState({ - textInputValue: evt.target.value, - isTextInvalid: !date.isValid() - }); + this.setState(updatedState); } render() { @@ -52,7 +53,7 @@ export class EuiAbsoluteTab extends Component { inline showTimeSelect shadow={false} - selected={this.state.value} + selected={this.state.valueAsMoment} onChange={this.handleChange} /> { - const relativeParts = parseRelativeParts(nextProps.value); - // parseRelativeParts will always return unit of 's' when the count is zero - // Do not override state.unit when count is zero to allow users to set the unit when count is zero - if (relativeParts.count === 0) { - delete relativeParts.unit; - } - return { - ...relativeParts, - }; - } - onCountChange = (evt) => { const sanitizedValue = parseInt(evt.target.value, 10); this.setState({ diff --git a/src/components/date_picker/super_date_picker/index.js b/src/components/date_picker/super_date_picker/index.js index 35eb266d870..beb571ca393 100644 --- a/src/components/date_picker/super_date_picker/index.js +++ b/src/components/date_picker/super_date_picker/index.js @@ -1,3 +1,5 @@ -export { - EuiSuperDatePicker, -} from './super_date_picker'; +import { + WrappedEuiSuperDatePicker, +} from './wrapped_super_date_picker'; + +export { WrappedEuiSuperDatePicker as EuiSuperDatePicker }; diff --git a/src/components/date_picker/super_date_picker/pretty_duration.js b/src/components/date_picker/super_date_picker/pretty_duration.js index ed357c1841b..feb9fe2ff58 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.js +++ b/src/components/date_picker/super_date_picker/pretty_duration.js @@ -5,7 +5,7 @@ import { timeUnits, timeUnitsPlural } from './time_units'; import { getDateMode, DATE_MODES } from './date_modes'; import { parseRelativeParts } from './relative_utils'; -const ISO_FORMAT = 'YYYY-MM-DDTHH:mm:ss.sssZ'; +const ISO_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; function cantLookup(timeFrom, timeTo, dateFormat) { const displayFrom = formatTimeString(timeFrom, dateFormat); diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js b/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js index 8c0ecc1d1d1..59f741b060c 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js @@ -35,11 +35,11 @@ function convertMilliseconds(milliseconds) { export class EuiRefreshInterval extends Component { - state = {} + constructor(props) { + super(props); - static getDerivedStateFromProps = (nextProps) => { - const { value, units } = convertMilliseconds(nextProps.refreshInterval); - return { + const { value, units } = convertMilliseconds(props.refreshInterval); + this.state = { value, units, }; diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index e331f1a7483..1ac6ceb7c69 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -35,22 +35,6 @@ export class EuiSuperDatePicker extends Component { }; } - static getDerivedStateFromProps = (nextProps) => { - const { - start, - end, - commonlyUsedRanges - } = nextProps; - - return { - start, - end, - isInvalid: false, - hasChanged: false, - showPrettyDuration: showPrettyDuration(start, end, commonlyUsedRanges), - }; - } - setTootipRef = node => (this.tooltip = node); showTooltip = () => this.tooltip.showToolTip(); diff --git a/src/components/date_picker/super_date_picker/wrapped_super_date_picker.js b/src/components/date_picker/super_date_picker/wrapped_super_date_picker.js new file mode 100644 index 00000000000..f9bbbaad52e --- /dev/null +++ b/src/components/date_picker/super_date_picker/wrapped_super_date_picker.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { EuiSuperDatePicker } from './super_date_picker'; + +// EuiSuperDatePicker has state that needs to be reset when start or end change. +// Instead of using getDerivedStateFromProps, this wrapper adds a key to the component. +// When a key changes, React will create a new component instance rather than update the current one +// https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key +export function WrappedEuiSuperDatePicker(props) { + return ( + + ); +} + +WrappedEuiSuperDatePicker.defaultProps = { + start: 'now-15m', + end: 'now', +}; From 97bc8726f83d2a125ccd78ff45a65d407aac6e72 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 14 Dec 2018 14:33:56 -0700 Subject: [PATCH 34/37] move changelog message under master section --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df6a2f43c83..f334039e324 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## [`master`](https://github.com/elastic/eui/tree/master) - Added `DisambiguateSet` and `ExclusiveUnion` utility types ([#1368](https://github.com/elastic/eui/pull/1368)) +- Added `EuiSuperDatePicker` component ([#1351](https://github.com/elastic/eui/pull/1351)) **Bug fixes** @@ -40,7 +41,6 @@ - Convert `EuiIcon` to TypeScript ([#1355](https://github.com/elastic/eui/pull/1355)) - Add support for `aria-label`, `aria-labelledby` and `aria-describedby` to `EuiCodeEditor` ([#1354](https://github.com/elastic/eui/pull/1354)) -- Added `EuiSuperDatePicker` component ([#1351](https://github.com/elastic/eui/pull/1351)) **Bug fixes** From df7fc4f3938539b52eefbfb8993c00fd6d6eeb87 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 14 Dec 2018 15:01:19 -0700 Subject: [PATCH 35/37] moved prop types to Wrapped component so they are usable in docs --- .../date_picker/super_date_picker/index.js | 2 +- .../super_date_picker/super_date_picker.js | 132 ++++++++++-------- .../wrapped_super_date_picker.js | 20 --- 3 files changed, 75 insertions(+), 79 deletions(-) delete mode 100644 src/components/date_picker/super_date_picker/wrapped_super_date_picker.js diff --git a/src/components/date_picker/super_date_picker/index.js b/src/components/date_picker/super_date_picker/index.js index beb571ca393..183a5d85800 100644 --- a/src/components/date_picker/super_date_picker/index.js +++ b/src/components/date_picker/super_date_picker/index.js @@ -1,5 +1,5 @@ import { WrappedEuiSuperDatePicker, -} from './wrapped_super_date_picker'; +} from './super_date_picker'; export { WrappedEuiSuperDatePicker as EuiSuperDatePicker }; diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index 1ac6ceb7c69..9b12ebc371f 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -15,6 +15,78 @@ import { EuiButton, EuiButtonEmpty } from '../../button'; import { EuiFlexGroup, EuiFlexItem } from '../../flex'; import { EuiToolTip } from '../../tool_tip'; +// EuiSuperDatePicker has state that needs to be reset when start or end change. +// Instead of using getDerivedStateFromProps, this wrapper adds a key to the component. +// When a key changes, React will create a new component instance rather than update the current one +// https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key +export function WrappedEuiSuperDatePicker(props) { + return ( + + ); +} + +WrappedEuiSuperDatePicker.propTypes = { + isLoading: PropTypes.bool, + /** + * String as either datemath (e.g.: now, now-15m, now-15m/m) or + * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.sssZ' + */ + start: PropTypes.string, + /** + * String as either datemath (e.g.: now, now-15m, now-15m/m) or + * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.sssZ' + */ + end: PropTypes.string, + /** + * Callback for when the time changes. Called with { start, end } + */ + onTimeChange: PropTypes.func.isRequired, + isPaused: PropTypes.bool, + /** + * Refresh interval in milliseconds + */ + refreshInterval: PropTypes.number, + /** + * Callback for when the refresh interval changes. Called with { isPaused, refreshInterval } + * Supply onRefreshChange to show refresh interval inputs in quick select popover + */ + onRefreshChange: PropTypes.func, + + /** + * 'start' and 'end' must be string as either datemath (e.g.: now, now-15m, now-15m/m) or + * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.sssZ' + */ + commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRangeShape), + dateFormat: PropTypes.string, + /** + * 'start' and 'end' must be string as either datemath (e.g.: now, now-15m, now-15m/m) or + * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.sssZ' + */ + recentlyUsedRanges: PropTypes.arrayOf(recentlyUsedRangeShape), +}; + +WrappedEuiSuperDatePicker.defaultProps = { + start: 'now-15m', + end: 'now', + isPaused: true, + refreshInterval: 0, + commonlyUsedRanges: [ + { start: 'now/d', end: 'now/d', label: 'Today' }, + { start: 'now-1d/d', end: 'now-1d/d', label: 'Yesterday' }, + { start: 'now/w', end: 'now/w', label: 'This week' }, + { start: 'now/w', end: 'now', label: 'Week to date' }, + { start: 'now/M', end: 'now/M', label: 'This month' }, + { start: 'now/M', end: 'now', label: 'Month to date' }, + { start: 'now/y', end: 'now/y', label: 'This year' }, + { start: 'now/y', end: 'now', label: 'Year to date' }, + ], + dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', + recentlyUsedRanges: [], +}; + export class EuiSuperDatePicker extends Component { constructor(props) { @@ -216,62 +288,6 @@ export class EuiSuperDatePicker extends Component { } } -EuiSuperDatePicker.propTypes = { - isLoading: PropTypes.bool, - /** - * String as either datemath (e.g.: now, now-15m, now-15m/m) or - * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.sssZ' - */ - start: PropTypes.string, - /** - * String as either datemath (e.g.: now, now-15m, now-15m/m) or - * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.sssZ' - */ - end: PropTypes.string, - /** - * Callback for when the time changes. Called with { start, end } - */ - onTimeChange: PropTypes.func.isRequired, - isPaused: PropTypes.bool, - /** - * Refresh interval in milliseconds - */ - refreshInterval: PropTypes.number, - /** - * Callback for when the refresh interval changes. Called with { isPaused, refreshInterval } - * Supply onRefreshChange to show refresh interval inputs in quick select popover - */ - onRefreshChange: PropTypes.func, - - /** - * 'start' and 'end' must be string as either datemath (e.g.: now, now-15m, now-15m/m) or - * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.sssZ' - */ - commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRangeShape), - dateFormat: PropTypes.string, - /** - * 'start' and 'end' must be string as either datemath (e.g.: now, now-15m, now-15m/m) or - * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.sssZ' - */ - recentlyUsedRanges: PropTypes.arrayOf(recentlyUsedRangeShape), -}; - -EuiSuperDatePicker.defaultProps = { - start: 'now-15m', - end: 'now', - isPaused: true, - refreshInterval: 0, - commonlyUsedRanges: [ - { start: 'now/d', end: 'now/d', label: 'Today' }, - { start: 'now-1d/d', end: 'now-1d/d', label: 'Yesterday' }, - { start: 'now/w', end: 'now/w', label: 'This week' }, - { start: 'now/w', end: 'now', label: 'Week to date' }, - { start: 'now/M', end: 'now/M', label: 'This month' }, - { start: 'now/M', end: 'now', label: 'Month to date' }, - { start: 'now/y', end: 'now/y', label: 'This year' }, - { start: 'now/y', end: 'now', label: 'Year to date' }, - ], - dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', - recentlyUsedRanges: [], -}; +EuiSuperDatePicker.propTypes = WrappedEuiSuperDatePicker.propTypes; +EuiSuperDatePicker.defaultProps = WrappedEuiSuperDatePicker.defaultProps; diff --git a/src/components/date_picker/super_date_picker/wrapped_super_date_picker.js b/src/components/date_picker/super_date_picker/wrapped_super_date_picker.js deleted file mode 100644 index f9bbbaad52e..00000000000 --- a/src/components/date_picker/super_date_picker/wrapped_super_date_picker.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import { EuiSuperDatePicker } from './super_date_picker'; - -// EuiSuperDatePicker has state that needs to be reset when start or end change. -// Instead of using getDerivedStateFromProps, this wrapper adds a key to the component. -// When a key changes, React will create a new component instance rather than update the current one -// https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key -export function WrappedEuiSuperDatePicker(props) { - return ( - - ); -} - -WrappedEuiSuperDatePicker.defaultProps = { - start: 'now-15m', - end: 'now', -}; From 88236cb41d1722f7564c9cfed23272bd7fc7d4d8 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 14 Dec 2018 15:19:55 -0700 Subject: [PATCH 36/37] check component is mounted for show and hide tooltip --- .../super_date_picker/super_date_picker.js | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.js index 9b12ebc371f..97756ccd20b 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.js @@ -107,10 +107,28 @@ export class EuiSuperDatePicker extends Component { }; } + componentWillUnmount() { + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + } + setTootipRef = node => (this.tooltip = node); - showTooltip = () => this.tooltip.showToolTip(); - hideTooltip = () => this.tooltip.hideToolTip(); + showTooltip = () => { + if (!this._isMounted) { + return; + } + this.tooltip.showToolTip(); + } + hideTooltip = () => { + if (!this._isMounted) { + return; + } + this.tooltip.hideToolTip(); + } setTime = ({ start, end }) => { const startMoment = dateMath.parse(start); From 95100c9c53232bf4b49e24cf192690cb630f12b2 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 14 Dec 2018 17:09:51 -0700 Subject: [PATCH 37/37] update to datemath dependency with moment as peer --- package.json | 3 ++- yarn.lock | 21 ++++----------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index c30bf4912e5..75d156b0970 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "url": "https://github.com/elastic/eui.git" }, "dependencies": { - "@elastic/datemath": "5.0.0", "@types/numeral": "^0.0.25", "classnames": "^2.2.5", "core-js": "^2.5.1", @@ -70,6 +69,7 @@ "@babel/preset-env": "^7.1.0", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.1.0", + "@elastic/datemath": "^5.0.2", "@elastic/eslint-config-kibana": "^0.15.0", "@types/classnames": "^2.2.6", "@types/enzyme": "^3.1.13", @@ -171,6 +171,7 @@ "yo": "^2.0.0" }, "peerDependencies": { + "@elastic/datemath": "^5.0.2", "moment": "^2.13.0", "prop-types": "^15.5.0", "react": "^16.3", diff --git a/yarn.lock b/yarn.lock index 8be47d609d4..28e52972457 100644 --- a/yarn.lock +++ b/yarn.lock @@ -780,12 +780,11 @@ lodash "^4.17.10" to-fast-properties "^2.0.0" -"@elastic/datemath@5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@elastic/datemath/-/datemath-5.0.0.tgz#62900ddbdc1c98b2af824073be462c769c582a34" - integrity sha512-csUO6qyWK0LKrX9V8WUnZZ2BBy0pB1nf8nNKw+KQz+PbKQeCr30jtk+2+z/u4DWMCQKODa5V5E8C8f9eNmkD9Q== +"@elastic/datemath@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@elastic/datemath/-/datemath-5.0.2.tgz#1e62fe7137acd6ebcde9a794ef22b91820c9e6cf" + integrity sha512-MYU7KedGPMYu3ljgrO3tY8I8rD73lvBCltd78k5avDIv/6gMbuhKXsMhkEPbb9angs9hR/2ADk0QcGbVxUBXUw== dependencies: - moment "^2.13.0" tslib "^1.9.3" "@elastic/eslint-config-kibana@^0.15.0": @@ -8813,23 +8812,11 @@ mocha@^5.0.0, mocha@^5.0.4: mkdirp "0.5.1" supports-color "4.4.0" -moment-timezone@^0.5.14: - version "0.5.23" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.23.tgz#7cbb00db2c14c71b19303cb47b0fb0a6d8651463" - integrity sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w== - dependencies: - moment ">= 2.9.0" - moment@2.x.x: version "2.21.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a" integrity sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ== -"moment@>= 2.9.0", moment@^2.13.0: - version "2.22.2" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" - integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= - moment@^2.20.1: version "2.20.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.20.1.tgz#d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd"