diff --git a/src/legacy/core_plugins/kibana/ui_setting_defaults.js b/src/legacy/core_plugins/kibana/ui_setting_defaults.js index 44e8848fd1368..1967b8d74bbbd 100644 --- a/src/legacy/core_plugins/kibana/ui_setting_defaults.js +++ b/src/legacy/core_plugins/kibana/ui_setting_defaults.js @@ -69,7 +69,7 @@ export function getUiSettingDefaults() { }, 'dateFormat': { name: 'Date format', - value: 'MMMM Do YYYY, HH:mm:ss.SSS', + value: 'MMM D, YYYY @ HH:mm:ss.SSS', description: `When displaying a pretty formatted date, use this format`, }, @@ -410,23 +410,6 @@ export function getUiSettingDefaults() { { from: 'now/w', to: 'now', display: 'Week to date', section: 0 }, { from: 'now/M', to: 'now', display: 'Month to date', section: 0 }, { from: 'now/y', to: 'now', display: 'Year to date', section: 0 }, - - { from: 'now-15m', to: 'now', display: 'Last 15 minutes', section: 1 }, - { from: 'now-30m', to: 'now', display: 'Last 30 minutes', section: 1 }, - { from: 'now-1h', to: 'now', display: 'Last 1 hour', section: 1 }, - { from: 'now-4h', to: 'now', display: 'Last 4 hours', section: 1 }, - { from: 'now-12h', to: 'now', display: 'Last 12 hours', section: 1 }, - { from: 'now-24h', to: 'now', display: 'Last 24 hours', section: 1 }, - { from: 'now-7d', to: 'now', display: 'Last 7 days', section: 1 }, - - { from: 'now-30d', to: 'now', display: 'Last 30 days', section: 2 }, - { from: 'now-60d', to: 'now', display: 'Last 60 days', section: 2 }, - { from: 'now-90d', to: 'now', display: 'Last 90 days', section: 2 }, - { from: 'now-6M', to: 'now', display: 'Last 6 months', section: 2 }, - { from: 'now-1y', to: 'now', display: 'Last 1 year', section: 2 }, - { from: 'now-2y', to: 'now', display: 'Last 2 years', section: 2 }, - { from: 'now-5y', to: 'now', display: 'Last 5 years', section: 2 }, - ], null, 2), type: 'json', description: `The list of ranges to show in the Quick section of the time picker. diff --git a/src/ui/public/react_components.js b/src/ui/public/react_components.js index 3475eefba926e..76e973441650b 100644 --- a/src/ui/public/react_components.js +++ b/src/ui/public/react_components.js @@ -23,6 +23,8 @@ import { KuiToolBarSearchBox, } from '@kbn/ui-framework/components'; +import { Timepicker } from 'ui/timepicker'; + import { EuiConfirmModal, EuiIcon, @@ -46,3 +48,6 @@ app.directive('colorPicker', reactDirective => reactDirective(EuiColorPicker)); app.directive('iconTip', reactDirective => reactDirective(EuiIconTip, ['content', 'type', 'position', 'title', 'color'])); app.directive('callOut', reactDirective => reactDirective(EuiCallOut, ['title', 'color', 'size', 'iconType', 'children'])); + +app.directive('newKbnTimepicker', reactDirective => reactDirective(Timepicker, [ + 'from', 'to', 'setTime', 'setRefresh', 'isPaused', 'refreshInterval'])); diff --git a/src/ui/public/timefilter/timefilter.js b/src/ui/public/timefilter/timefilter.js index 8f2bbd799efa4..11ae9cf76ba82 100644 --- a/src/ui/public/timefilter/timefilter.js +++ b/src/ui/public/timefilter/timefilter.js @@ -54,6 +54,7 @@ class Timefilter extends SimpleEmitter { * @property {string} time.mode (quick | relative | absolute) */ setTime = (time) => { + console.log(time); // Object.assign used for partially composed updates const newTime = Object.assign(this.getTime(), time); if (areTimePickerValsDifferent(this.getTime(), newTime)) { diff --git a/src/ui/public/timepicker/components/absolute_form.js b/src/ui/public/timepicker/components/absolute_form.js new file mode 100644 index 0000000000000..d100759b68eb9 --- /dev/null +++ b/src/ui/public/timepicker/components/absolute_form.js @@ -0,0 +1,106 @@ +/* + * 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, Fragment } from 'react'; + +import moment from 'moment'; + +import dateMath from '@elastic/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/ui/public/timepicker/components/quick_form.js b/src/ui/public/timepicker/components/quick_form.js new file mode 100644 index 0000000000000..dd6ba1e77f133 --- /dev/null +++ b/src/ui/public/timepicker/components/quick_form.js @@ -0,0 +1,327 @@ +/* + * 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 _ from 'lodash'; +import chrome from 'ui/chrome'; +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, + EuiIcon, + EuiPopover, + EuiTitle, + EuiSpacer, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSelect, + EuiFieldNumber, + EuiButton, + EuiText, + EuiHorizontalRule, + EuiLink, + EuiButtonIcon, +} from '@elastic/eui'; + +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 QuickForm 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 ( + +
+ + + {this.renderTimeNavigation()} + + {this.renderQuickSelect()} + + {this.renderCommonlyUsed()} + + {this.renderRecentlyUsed()} +
+
+ ); + } +} + +QuickForm.propTypes = { + applyTime: PropTypes.func.isRequired, + setTime: PropTypes.func.isRequired, + stepForward: PropTypes.func.isRequired, + stepBackward: PropTypes.func.isRequired, + setRefresh: PropTypes.func, + isPaused: PropTypes.bool, + refreshInterval: PropTypes.number, +}; diff --git a/src/ui/public/timepicker/components/refresh_interval_form.js b/src/ui/public/timepicker/components/refresh_interval_form.js new file mode 100644 index 0000000000000..f17a46b6a2344 --- /dev/null +++ b/src/ui/public/timepicker/components/refresh_interval_form.js @@ -0,0 +1,172 @@ +/* + * 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'; + +import { timeUnits } from '../time_units'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSelect, + EuiFieldNumber, + EuiButtonIcon, + EuiButton, +} from '@elastic/eui'; + +const refreshUnitsOptions = Object.keys(timeUnits) + .map(key => { + return { value: key, text: `${timeUnits[key]}s` }; + }) + .filter(option => { + return option.value === 'h' || option.value === 'm'; + }); + +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 RefreshIntervalForm 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, + }; + } + + toogleRefresh = () => { + this.props.setRefresh({ + pause: !this.props.isPaused + }); + } + + onValueChange = (evt) => { + const sanitizedValue = parseInt(evt.target.value, 10); + this.setState({ + value: isNaN(sanitizedValue) ? 0 : sanitizedValue, + }); + }; + + onUnitsChange = (evt) => { + this.setState({ + units: evt.target.value, + }); + } + + setRefresh = () => { + let value; + if (this.state.units === 'h') { + value = this.state.value * MILLISECONDS_IN_HOUR; + } else { + value = this.state.value * MILLISECONDS_IN_MINUTE; + } + this.props.setRefresh({ + value, + }); + } + + render() { + if (!this.props.setRefresh) { + return; + } + + return ( + + + + + + + + + + + + + + + + + + + + Apply + + + + + ); + } +} + +RefreshIntervalForm.propTypes = { + setRefresh: PropTypes.func, + isPaused: PropTypes.bool, + refreshInterval: PropTypes.number, +}; + +RefreshIntervalForm.defaultProps = { + isPaused: true, + refreshInterval: 0, +}; + diff --git a/src/ui/public/timepicker/components/relative_form.js b/src/ui/public/timepicker/components/relative_form.js new file mode 100644 index 0000000000000..7ebb6f57f8a2e --- /dev/null +++ b/src/ui/public/timepicker/components/relative_form.js @@ -0,0 +1,125 @@ +/* + * 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'; +import dateMath from '@elastic/datemath'; + +import { + EuiForm, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiFieldNumber, + EuiSelect, + EuiDatePicker, + EuiSwitch, +} from '@elastic/eui'; + +import { timeUnits } from '../time_units'; +import { parseRelativeString } from '../parse_relative_parts'; +import { relativeOptions } from '../relative_options'; +import { toRelativeStringFromParts } from '../lib/time_modes'; + +export class RelativeForm extends Component { + + constructor(props) { + super(props); + + this.state = { + ...parseRelativeString(this.props.value) + }; + } + + onCountChange = (evt) => { + const sanitizedValue = parseInt(evt.target.value, 10); + this.setState({ + count: isNaN(sanitizedValue) ? '' : sanitizedValue, + }, this.handleChange); + } + + onUnitChange = (evt) => { + this.setState({ + unit: evt.target.value, + }, this.handleChange); + } + + onRoundChange = (evt) => { + this.setState({ + round: evt.target.checked, + }, this.handleChange); + }; + + handleChange = () => { + const { + count, + unit, + round, + } = this.state; + this.props.onChange(toRelativeStringFromParts({ count, unit, round })); + } + + render() { + return ( + + + + + + + + + + + + + + + + + + + + + ); + } +} + +RelativeForm.propTypes = { + dateFormat: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + roundUp: PropTypes.bool, +}; diff --git a/src/ui/public/timepicker/components/time_input.js b/src/ui/public/timepicker/components/time_input.js new file mode 100644 index 0000000000000..b5f7544d5a666 --- /dev/null +++ b/src/ui/public/timepicker/components/time_input.js @@ -0,0 +1,154 @@ +/* + * 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 TimeInput 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 ( + + + + ); + } +} + +TimeInput.propTypes = { + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + roundUp: PropTypes.bool, +}; + +TimeInput.defaultProps = { + roundUp: false, +}; diff --git a/src/ui/public/timepicker/components/timepicker.js b/src/ui/public/timepicker/components/timepicker.js new file mode 100644 index 0000000000000..00dd846c3f2e2 --- /dev/null +++ b/src/ui/public/timepicker/components/timepicker.js @@ -0,0 +1,250 @@ +/* + * 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 moment from 'moment'; +import PropTypes from 'prop-types'; +import React, { Component, Fragment } from 'react'; + +import dateMath from '@elastic/datemath'; + +import { QuickForm } from './quick_form'; +import { TimeInput } from './time_input'; +import { toastNotifications } from 'ui/notify'; + +import { + EuiText, + EuiFormControlLayout, + EuiButton, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; + +import { prettyDuration } from '../pretty_duration'; +import { timeNavigation } from '../time_navigation'; +import { calculateBounds } from 'ui/timefilter/get_time'; + +export class Timepicker 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 }) => { + if (this.lastToast) { + toastNotifications.remove(this.lastToast); + } + + 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, + 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())); + } + + applyTimeFromState = () => { + this.props.setTime(this.state.from, this.state.to); + } + + applyTime = ({ from, to }) => { + this.props.setTime(from, to); + } + + toTimeString = (timeValue) => { + if (moment.isMoment(timeValue)) { + return timeValue.toISOString(); + } + + return timeValue; + } + + editMode = () => { + this.setState({ + isEditMode: true + }); + } + + renderTime = () => { + const from = this.toTimeString(this.state.from); + const to = this.toTimeString(this.state.to); + if (this.state.isEditMode || this.state.hasChanged) { + return ( + + + + + + ); + } + + const getConfig = (...args) => chrome.getUiSettingsClient().get(...args); + return ( + + ); + } + + render() { + let updateButton; + if (this.state.isEditMode || this.state.hasChanged) { + updateButton = ( + + + Update + + + ); + } else { + updateButton = ( + + + Refresh + + + ); + } + return ( + + + + )} + > +
+ {this.renderTime()} +
+
+
+ + {updateButton} + +
+ ); + } +} + +const timeType = PropTypes.oneOfType([ + PropTypes.string, + PropTypes.object, +]); + +Timepicker.propTypes = { + from: timeType, + to: timeType, + setTime: PropTypes.func, + setRefresh: PropTypes.func, + isPaused: PropTypes.bool, + refreshInterval: PropTypes.number, +}; + +Timepicker.defaultProps = { + from: 'now-15m', + to: 'now', +}; + diff --git a/src/ui/public/timepicker/index.js b/src/ui/public/timepicker/index.js index 2cea4af1a5430..6e4ba87206e6a 100644 --- a/src/ui/public/timepicker/index.js +++ b/src/ui/public/timepicker/index.js @@ -18,3 +18,4 @@ */ import './timepicker'; +export { Timepicker } from './components/timepicker'; diff --git a/src/ui/public/timepicker/kbn_global_timepicker.html b/src/ui/public/timepicker/kbn_global_timepicker.html index 0cc651f98951d..b76ea9ef3c438 100644 --- a/src/ui/public/timepicker/kbn_global_timepicker.html +++ b/src/ui/public/timepicker/kbn_global_timepicker.html @@ -5,80 +5,12 @@ class="kuiLocalMenu" data-test-subj="globalTimepicker" > - - - - - - - - - + diff --git a/src/ui/public/timepicker/kbn_global_timepicker.js b/src/ui/public/timepicker/kbn_global_timepicker.js index 8d4dcbae0be16..1b2905871e18c 100644 --- a/src/ui/public/timepicker/kbn_global_timepicker.js +++ b/src/ui/public/timepicker/kbn_global_timepicker.js @@ -20,7 +20,6 @@ import { uiModules } from '../modules'; import toggleHtml from './kbn_global_timepicker.html'; -import { timeNavigation } from './time_navigation'; import { timefilter } from 'ui/timefilter'; import { prettyDuration } from './pretty_duration'; import { prettyInterval } from './pretty_interval'; @@ -70,14 +69,6 @@ uiModules timefilter.toggleRefresh(); }; - $scope.forward = function () { - timefilter.setTime(timeNavigation.stepForward(timefilter.getBounds())); - }; - - $scope.back = function () { - timefilter.setTime(timeNavigation.stepBackward(timefilter.getBounds())); - }; - $scope.updateFilter = function (from, to, mode) { timefilter.setTime({ from, to, mode }); kbnTopNav.close('filter'); diff --git a/src/ui/public/timepicker/lib/time_modes.js b/src/ui/public/timepicker/lib/time_modes.js new file mode 100644 index 0000000000000..9e0526dc8a639 --- /dev/null +++ b/src/ui/public/timepicker/lib/time_modes.js @@ -0,0 +1,63 @@ +/* + * 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 _ from 'lodash'; +import dateMath from '@elastic/datemath'; +import { parseRelativeString } from '../parse_relative_parts'; + +export const TIME_MODES = { + ABSOLUTE: 'absolute', + RELATIVE: 'relative', + NOW: 'now', +}; + +export function getTimeMode(value) { + if (value === 'now') { + return TIME_MODES.NOW; + } + + if (value.includes('now')) { + return TIME_MODES.RELATIVE; + } + + return TIME_MODES.absolute; +} + +export function toAbsoluteString(value, roundUp) { + return dateMath.parse(value, { roundUp }).toISOString(); +} + + +export function toRelativeString(value) { + return toRelativeStringFromParts(parseRelativeString(value)); +} + +export function toRelativeStringFromParts(relativeParts) { + const count = _.get(relativeParts, `count`, 0); + const round = _.get(relativeParts, `round`, false); + const matches = _.get(relativeParts, `unit`, 's').match(/([smhdwMy])(\+)?/); + let unit; + let operator = '-'; + if (matches && matches[1]) unit = matches[1]; + if (matches && matches[2]) operator = matches[2]; + if (count === 0 && !round) return 'now'; + let result = `now${operator}${count}${unit}`; + result += (round ? '/' + unit : ''); + return result; +} diff --git a/src/ui/public/timepicker/pretty_duration.js b/src/ui/public/timepicker/pretty_duration.js index f2279cdea721a..96a4eeed21317 100644 --- a/src/ui/public/timepicker/pretty_duration.js +++ b/src/ui/public/timepicker/pretty_duration.js @@ -27,7 +27,7 @@ function cantLookup(timeFrom, timeTo, dateFormat) { return `${displayFrom} to ${displayTo}`; } -function formatTimeString(timeString, dateFormat, roundUp = false) { +export function formatTimeString(timeString, dateFormat, roundUp = false) { if (moment(timeString).isValid()) { return moment(timeString).format(dateFormat); } else {