/*
* 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 (