/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. * * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ /* * 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 React, { Component } from 'react'; import classNames from 'classnames'; import { prettyDuration, showPrettyDuration, commonDurationRanges, } from './pretty_duration'; import { prettyInterval } from './pretty_interval'; import dateMath from '@opensearch/datemath'; import { OuiSuperUpdateButton, OuiSuperUpdateButtonProps, } from './super_update_button'; import { OuiQuickSelectPopover } from './quick_select_popover/quick_select_popover'; import { OuiDatePopoverButton } from './date_popover/date_popover_button'; import { OuiDatePickerRange } from '../date_picker_range'; import { OuiFormControlLayout } from '../../form'; import { OuiFlexGroup, OuiFlexItem } from '../../flex'; import { AsyncInterval } from './async_interval'; import { OuiI18n } from '../../i18n'; import { OuiI18nConsumer } from '../../context'; import { CommonProps } from '../../common'; import { ShortDate, Milliseconds, DurationRange, ApplyTime, ApplyRefreshInterval, QuickSelectPanel, } from '../types'; import { OuiDatePopoverContentProps } from './date_popover/date_popover_content'; import { LocaleSpecifier } from 'moment'; // eslint-disable-line import/named export { prettyDuration, commonDurationRanges }; export interface OnTimeChangeProps extends DurationRange { isInvalid: boolean; isQuickSelection: boolean; } export interface OnRefreshProps extends DurationRange { refreshInterval: number; } export type OuiSuperDatePickerProps = CommonProps & { commonlyUsedRanges: DurationRange[]; customQuickSelectPanels?: QuickSelectPanel[]; /** * Specifies the formatted used when displaying dates and/or datetimes */ dateFormat: string; end: ShortDate; /** * Set isAutoRefreshOnly to true to limit the component to only display auto refresh content. */ isAutoRefreshOnly: boolean; isDisabled: boolean; isLoading?: boolean; isPaused: boolean; /** * Used to localize e.g. month names, passed to `moment` */ locale?: LocaleSpecifier; /** * Callback for when the refresh interval is fired. * OuiSuperDatePicker will only manage a refresh interval timer when onRefresh callback is supplied * If a promise is returned, the next refresh interval will not start until the promise has resolved. * If the promise rejects the refresh interval will stop and the error thrown */ onRefresh?: (props: OnRefreshProps) => void; /** * Callback for when the refresh interval changes. * Supply onRefreshChange to show refresh interval inputs in quick select popover */ onRefreshChange?: ApplyRefreshInterval; /** * Callback for when the time changes. */ onTimeChange: (props: OnTimeChangeProps) => void; recentlyUsedRanges: DurationRange[]; /** * Refresh interval in milliseconds */ refreshInterval: Milliseconds; /** * Set showUpdateButton to false to immediately invoke onTimeChange for all start and end changes. */ showUpdateButton: boolean; start: ShortDate; /** * Specifies the formatted used when displaying times */ timeFormat: string; utcOffset?: number; /** * Props passed to the update button */ updateButtonProps?: Partial< Omit< OuiSuperUpdateButtonProps, 'needsUpdate' | 'showTooltip' | 'isLoading' | 'isDisabled' | 'onClick' > >; }; interface OuiSuperDatePickerState { end: ShortDate; hasChanged: boolean; isEndDatePopoverOpen: boolean; isInvalid: boolean; isStartDatePopoverOpen: boolean; prevProps: { end: ShortDate; start: ShortDate; }; showPrettyDuration: boolean; start: ShortDate; } function isRangeInvalid(start: ShortDate, end: ShortDate) { if (start === 'now' && end === 'now') { return true; } const startMoment = dateMath.parse(start); const endMoment = dateMath.parse(end, { roundUp: true }); if ( !startMoment || !endMoment || !startMoment.isValid() || !endMoment.isValid() ) { return true; } if (startMoment.isAfter(endMoment)) { return true; } return false; } export class OuiSuperDatePicker extends Component< OuiSuperDatePickerProps, OuiSuperDatePickerState > { static defaultProps = { commonlyUsedRanges: commonDurationRanges, dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', end: 'now', isAutoRefreshOnly: false, isDisabled: false, isPaused: true, recentlyUsedRanges: [], refreshInterval: 0, showUpdateButton: true, start: 'now-15m', timeFormat: 'HH:mm', }; asyncInterval?: AsyncInterval; state: OuiSuperDatePickerState = { prevProps: { start: this.props.start, end: this.props.end, }, start: this.props.start, end: this.props.end, isInvalid: isRangeInvalid(this.props.start, this.props.end), hasChanged: false, showPrettyDuration: showPrettyDuration( this.props.start, this.props.end, this.props.commonlyUsedRanges ), isStartDatePopoverOpen: false, isEndDatePopoverOpen: false, }; static getDerivedStateFromProps( nextProps: OuiSuperDatePickerProps, prevState: OuiSuperDatePickerState ) { if ( nextProps.start !== prevState.prevProps.start || nextProps.end !== prevState.prevProps.end ) { return { prevProps: { start: nextProps.start, end: nextProps.end, }, start: nextProps.start, end: nextProps.end, isInvalid: isRangeInvalid(nextProps.start, nextProps.end), hasChanged: false, showPrettyDuration: showPrettyDuration( nextProps.start, nextProps.end, nextProps.commonlyUsedRanges ), }; } return null; } setTime = ({ end, start }: DurationRange) => { const isInvalid = isRangeInvalid(start, end); this.setState({ start, end, isInvalid, hasChanged: !( this.state.prevProps.start === start && this.state.prevProps.end === end ), }); if (!this.props.showUpdateButton) { this.props.onTimeChange({ start, end, isQuickSelection: false, isInvalid, }); } }; componentDidMount = () => { if (!this.props.isPaused) { this.startInterval(this.props.refreshInterval); } }; componentDidUpdate = () => { this.stopInterval(); if (!this.props.isPaused) { this.startInterval(this.props.refreshInterval); } }; componentWillUnmount = () => { this.stopInterval(); }; setStart: OuiDatePopoverContentProps['onChange'] = (start: ShortDate) => { this.setTime({ start, end: this.state.end }); }; setEnd: OuiDatePopoverContentProps['onChange'] = (end: ShortDate) => { this.setTime({ start: this.state.start, end }); }; applyTime = () => { this.props.onTimeChange({ start: this.state.start, end: this.state.end, isQuickSelection: false, isInvalid: false, }); }; applyQuickTime: ApplyTime = ({ start, end }) => { this.setState({ showPrettyDuration: showPrettyDuration(start, end, commonDurationRanges), }); this.props.onTimeChange({ start, end, isQuickSelection: true, isInvalid: false, }); }; hidePrettyDuration = () => { this.setState({ showPrettyDuration: false, isStartDatePopoverOpen: true }); }; onStartDatePopoverToggle = () => { this.setState((prevState) => { return { isStartDatePopoverOpen: !prevState.isStartDatePopoverOpen }; }); }; onStartDatePopoverClose = () => { this.setState({ isStartDatePopoverOpen: false }); }; onEndDatePopoverToggle = () => { this.setState((prevState) => { return { isEndDatePopoverOpen: !prevState.isEndDatePopoverOpen }; }); }; onEndDatePopoverClose = () => { this.setState({ isEndDatePopoverOpen: false }); }; onRefreshChange: ApplyRefreshInterval = ({ refreshInterval, isPaused }) => { this.stopInterval(); if (!isPaused) { this.startInterval(refreshInterval); } if (this.props.onRefreshChange) { this.props.onRefreshChange({ refreshInterval, isPaused }); } }; stopInterval = () => { if (this.asyncInterval) { this.asyncInterval.stop(); } }; startInterval = (refreshInterval: number) => { const { onRefresh } = this.props; if (onRefresh) { const handler = () => { const { start, end } = this.props; onRefresh({ start, end, refreshInterval }); }; this.asyncInterval = new AsyncInterval(handler, refreshInterval); } }; renderDatePickerRange = () => { const { end, hasChanged, isEndDatePopoverOpen, isInvalid, isStartDatePopoverOpen, showPrettyDuration, start, } = this.state; const { commonlyUsedRanges, dateFormat, isAutoRefreshOnly, isDisabled, isPaused, locale, refreshInterval, timeFormat, utcOffset, } = this.props; if (isAutoRefreshOnly) { return ( } endDateControl={
} readOnly> {prettyInterval(Boolean(isPaused), refreshInterval)} ); } if ( showPrettyDuration && !isStartDatePopoverOpen && !isEndDatePopoverOpen ) { return ( } endDateControl={
}> ); } return ( {({ locale: contextLocale }) => ( } endDateControl={ } /> )} ); }; handleClickUpdateButton = () => { if (!this.state.hasChanged && this.props.onRefresh) { const { start, end, refreshInterval } = this.props; this.props.onRefresh({ start, end, refreshInterval }); } else { this.applyTime(); } }; renderUpdateButton = () => { if (!this.props.showUpdateButton || this.props.isAutoRefreshOnly) { return; } return ( ); }; render() { const { commonlyUsedRanges, customQuickSelectPanels, dateFormat, end, isAutoRefreshOnly, isDisabled, isPaused, onRefreshChange, recentlyUsedRanges, refreshInterval, showUpdateButton, start, } = this.props; const quickSelect = ( ); const flexWrapperClasses = classNames('ouiSuperDatePicker__flexWrapper', { 'ouiSuperDatePicker__flexWrapper--noUpdateButton': !showUpdateButton, 'ouiSuperDatePicker__flexWrapper--isAutoRefreshOnly': isAutoRefreshOnly, }); return ( {this.renderDatePickerRange()} {this.renderUpdateButton()} ); } }