[Canvas] Converting time filter (and children) component to typescript and adding to storybook (#40443)

* Converting time filter (and children) component to typescript and adding to storybook
This commit is contained in:
Poff Poffenberger 2019-07-12 16:48:46 -05:00 committed by GitHub
parent 8571d56839
commit 8be17fe616
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 2052 additions and 383 deletions

View file

@ -5,10 +5,16 @@
*/
import path from 'path';
import moment from 'moment';
import 'moment-timezone';
import initStoryshots, { multiSnapshotWithOptions } from '@storybook/addon-storyshots';
import styleSheetSerializer from 'jest-styled-components/src/styleSheetSerializer';
import { addSerializer } from 'jest-specific-snapshot';
// Set our default timezone to UTC for tests so we can generate predictable snapshots
moment.tz.setDefault('UTC');
// Mock EUI generated ids to be consistently predictable for snapshots.
jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`);
@ -25,6 +31,24 @@ jest.mock('../canvas_plugin_src/renderers/shape/shapes', () => ({
},
}));
// Mock datetime parsing so we can get stable results for tests (even while using the `now` format)
jest.mock('@elastic/datemath', () => {
return {
parse: (d, opts) => {
const dateMath = jest.requireActual('@elastic/datemath');
return dateMath.parse(d, {...opts, forceNow: new Date(Date.UTC(2019, 5, 1))}); // June 1 2019
}
}
});
// Mock react-datepicker dep used by eui to avoid rendering the entire large component
jest.mock('@elastic/eui/packages/react-datepicker', () => {
return {
__esModule: true,
default: 'ReactDatePicker',
}
});
addSerializer(styleSheetSerializer);
// Initialize Storyshots and build the Jest Snapshots

View file

@ -0,0 +1,335 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots renderers/TimeFilter/components/DatetimeCalendar default 1`] = `
<div
className="canvasDateTimeCal"
>
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
style={
Object {
"textAlign": "center",
}
}
type="text"
value=""
/>
</div>
</div>
<span>
<span
className="euiDatePicker euiDatePicker--inline"
>
<div
className="euiFormControlLayout"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<ReactDatePicker
accessibleMode={true}
adjustDateOnChange={true}
className="euiDatePicker euiFieldText"
dateFormat="MM/DD/YYYY hh:mm A"
inline={true}
onChange={[Function]}
selected={null}
shouldCloseOnSelect={false}
showMonthDropdown={true}
showTimeSelect={true}
showYearDropdown={true}
timeFormat="hh:mm A"
yearDropdownItemNumber={7}
/>
</div>
</div>
</span>
</span>
</div>
`;
exports[`Storyshots renderers/TimeFilter/components/DatetimeCalendar invalid date 1`] = `
<div
className="canvasDateTimeCal"
>
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
style={
Object {
"textAlign": "center",
}
}
type="text"
value="Invalid date"
/>
</div>
</div>
<span>
<span
className="euiDatePicker euiDatePicker--inline"
>
<div
className="euiFormControlLayout"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<ReactDatePicker
accessibleMode={true}
adjustDateOnChange={true}
className="euiDatePicker euiFieldText"
dateFormat="MM/DD/YYYY hh:mm A"
inline={true}
onChange={[Function]}
selected={null}
shouldCloseOnSelect={false}
showMonthDropdown={true}
showTimeSelect={true}
showYearDropdown={true}
timeFormat="hh:mm A"
yearDropdownItemNumber={7}
/>
</div>
</div>
</span>
</span>
</div>
`;
exports[`Storyshots renderers/TimeFilter/components/DatetimeCalendar with max date 1`] = `
<div
className="canvasDateTimeCal"
>
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
style={
Object {
"textAlign": "center",
}
}
type="text"
value=""
/>
</div>
</div>
<span>
<span
className="euiDatePicker euiDatePicker--inline"
>
<div
className="euiFormControlLayout"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<ReactDatePicker
accessibleMode={true}
adjustDateOnChange={true}
className="euiDatePicker euiFieldText"
dateFormat="MM/DD/YYYY hh:mm A"
inline={true}
maxDate={"2019-07-04T00:00:00.000Z"}
onChange={[Function]}
selected={null}
shouldCloseOnSelect={false}
showMonthDropdown={true}
showTimeSelect={true}
showYearDropdown={true}
timeFormat="hh:mm A"
yearDropdownItemNumber={7}
/>
</div>
</div>
</span>
</span>
</div>
`;
exports[`Storyshots renderers/TimeFilter/components/DatetimeCalendar with min date 1`] = `
<div
className="canvasDateTimeCal"
>
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
style={
Object {
"textAlign": "center",
}
}
type="text"
value=""
/>
</div>
</div>
<span>
<span
className="euiDatePicker euiDatePicker--inline"
>
<div
className="euiFormControlLayout"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<ReactDatePicker
accessibleMode={true}
adjustDateOnChange={true}
className="euiDatePicker euiFieldText"
dateFormat="MM/DD/YYYY hh:mm A"
inline={true}
minDate={"2019-07-04T00:00:00.000Z"}
onChange={[Function]}
selected={null}
shouldCloseOnSelect={false}
showMonthDropdown={true}
showTimeSelect={true}
showYearDropdown={true}
timeFormat="hh:mm A"
yearDropdownItemNumber={7}
/>
</div>
</div>
</span>
</span>
</div>
`;
exports[`Storyshots renderers/TimeFilter/components/DatetimeCalendar with start and end date 1`] = `
<div
className="canvasDateTimeCal"
>
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
style={
Object {
"textAlign": "center",
}
}
type="text"
value=""
/>
</div>
</div>
<span>
<span
className="euiDatePicker euiDatePicker--inline"
>
<div
className="euiFormControlLayout"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<ReactDatePicker
accessibleMode={true}
adjustDateOnChange={true}
className="euiDatePicker euiFieldText"
dateFormat="MM/DD/YYYY hh:mm A"
endDate={"2019-07-04T00:00:00.000Z"}
inline={true}
onChange={[Function]}
selected={null}
shouldCloseOnSelect={false}
showMonthDropdown={true}
showTimeSelect={true}
showYearDropdown={true}
startDate={"2019-06-27T00:00:00.000Z"}
timeFormat="hh:mm A"
yearDropdownItemNumber={7}
/>
</div>
</div>
</span>
</span>
</div>
`;
exports[`Storyshots renderers/TimeFilter/components/DatetimeCalendar with value 1`] = `
<div
className="canvasDateTimeCal"
>
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
style={
Object {
"textAlign": "center",
}
}
type="text"
value="2019-06-27 00:00:00"
/>
</div>
</div>
<span>
<span
className="euiDatePicker euiDatePicker--inline"
>
<div
className="euiFormControlLayout"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<ReactDatePicker
accessibleMode={true}
adjustDateOnChange={true}
className="euiDatePicker euiFieldText"
dateFormat="MM/DD/YYYY hh:mm A"
inline={true}
onChange={[Function]}
selected={"2019-06-27T00:00:00.000Z"}
shouldCloseOnSelect={false}
showMonthDropdown={true}
showTimeSelect={true}
showYearDropdown={true}
timeFormat="hh:mm A"
yearDropdownItemNumber={7}
/>
</div>
</div>
</span>
</span>
</div>
`;

View file

@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import moment from 'moment';
import React from 'react';
import { DatetimeCalendar } from '..';
const startDate = moment.utc('2019-06-27');
const endDate = moment.utc('2019-07-04');
storiesOf('renderers/TimeFilter/components/DatetimeCalendar', module)
.add('default', () => (
<DatetimeCalendar onSelect={action('onSelect')} onValueChange={action('onValueChange')} />
))
.add('with value', () => (
<DatetimeCalendar
value={startDate}
onSelect={action('onSelect')}
onValueChange={action('onValueChange')}
/>
))
.add('with start and end date', () => (
<DatetimeCalendar
startDate={startDate}
endDate={endDate}
onSelect={action('onSelect')}
onValueChange={action('onValueChange')}
/>
))
.add('with min date', () => (
<DatetimeCalendar
onSelect={action('onSelect')}
onValueChange={action('onValueChange')}
minDate={endDate}
/>
))
.add('with max date', () => (
<DatetimeCalendar
onSelect={action('onSelect')}
onValueChange={action('onValueChange')}
maxDate={endDate}
/>
))
.add('invalid date', () => (
<DatetimeCalendar
value={moment('foo')}
onSelect={action('onSelect')}
onValueChange={action('onValueChange')}
/>
));

View file

@ -1,47 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import dateMath from '@elastic/datemath';
import { EuiDatePicker } from '@elastic/eui';
import { DatetimeInput } from '../datetime_input';
export const DatetimeCalendar = ({
value,
onValueChange,
onSelect,
startDate,
endDate,
minDate,
maxDate,
}) => (
<div className="canvasDateTimeCal">
<DatetimeInput moment={dateMath.parse(value)} setMoment={onValueChange} />
<EuiDatePicker
inline
showTimeSelect
shadow={false}
selected={dateMath.parse(value)}
onChange={onSelect}
shouldCloseOnSelect={false}
startDate={startDate}
endDate={endDate}
minDate={minDate}
maxDate={maxDate}
/>
</div>
);
DatetimeCalendar.propTypes = {
value: PropTypes.object,
onSelect: PropTypes.func,
onValueChange: PropTypes.func, // Called with a moment
startDate: PropTypes.object, // a moment
endDate: PropTypes.object, // a moment
minDate: PropTypes.object, // a moment
maxDate: PropTypes.object, // a moment
};

View file

@ -0,0 +1,65 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { FunctionComponent } from 'react';
import PropTypes from 'prop-types';
import { Moment } from 'moment';
import { momentObj } from 'react-moment-proptypes';
import { EuiDatePicker } from '@elastic/eui';
import { DatetimeInput } from '../datetime_input';
export interface Props {
/** Selected date (Moment date object) */
value?: Moment;
/** Function invoked when a date is selected from the datepicker */
onSelect: (date: Moment | null) => void;
/** Function invoked when the date text input changes */
onValueChange: (moment: Moment) => void; // Called with a moment
/** Start date of selected date range (Moment date object) */
startDate?: Moment;
/** End date of selected date range (Moment date object) */
endDate?: Moment;
/** Earliest selectable date (Moment date object) */
minDate?: Moment;
/** Latest selectable date (Moment date object) */
maxDate?: Moment;
}
export const DatetimeCalendar: FunctionComponent<Props> = ({
value,
onValueChange,
onSelect,
startDate,
endDate,
minDate,
maxDate,
}) => (
<div className="canvasDateTimeCal">
<DatetimeInput moment={value} setMoment={onValueChange} />
<EuiDatePicker
inline
showTimeSelect
shadow={false}
selected={value && value.isValid() ? value : null}
onChange={onSelect}
shouldCloseOnSelect={false}
startDate={startDate}
endDate={endDate}
minDate={minDate}
maxDate={maxDate}
/>
</div>
);
DatetimeCalendar.propTypes = {
value: PropTypes.oneOfType([momentObj, PropTypes.object]), // Handle both valid and invalid moment objects
onSelect: PropTypes.func.isRequired,
onValueChange: PropTypes.func.isRequired, // Called with a moment
startDate: momentObj,
endDate: momentObj,
minDate: momentObj,
maxDate: momentObj,
};

View file

@ -4,12 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export const timeUnits = {
s: 'second',
m: 'minute',
h: 'hour',
d: 'day',
w: 'week',
M: 'month',
y: 'year',
};
export { DatetimeCalendar } from './datetime_calendar';

View file

@ -0,0 +1,67 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots renderers/TimeFilter/components/DatetimeInput default 1`] = `
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
style={
Object {
"textAlign": "center",
}
}
type="text"
value=""
/>
</div>
</div>
`;
exports[`Storyshots renderers/TimeFilter/components/DatetimeInput invalid date 1`] = `
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
style={
Object {
"textAlign": "center",
}
}
type="text"
value="Invalid date"
/>
</div>
</div>
`;
exports[`Storyshots renderers/TimeFilter/components/DatetimeInput with date 1`] = `
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
style={
Object {
"textAlign": "center",
}
}
type="text"
value="2018-02-20 19:26:52"
/>
</div>
</div>
`;

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import moment from 'moment';
import React from 'react';
import { DatetimeInput } from '..';
storiesOf('renderers/TimeFilter/components/DatetimeInput', module)
.add('default', () => <DatetimeInput setMoment={action('setMoment')} />)
.add('with date', () => (
<DatetimeInput moment={moment.utc('2018-02-20 19:26:52')} setMoment={action('setMoment')} />
))
.add('invalid date', () => (
<DatetimeInput moment={moment('foo')} setMoment={action('setMoment')} />
));

View file

@ -4,13 +4,32 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import React, { FunctionComponent, ChangeEvent } from 'react';
import PropTypes from 'prop-types';
import { EuiFieldText } from '@elastic/eui';
import moment from 'moment';
import moment, { Moment } from 'moment';
export const DatetimeInput = ({ strValue, setStrValue, setMoment, valid, setValid }) => {
function check(e) {
export interface Props {
/** Selected string value of input */
strValue: string;
/** Function invoked with string when input is changed */
setStrValue: (value: string) => void;
/** Function invoked with moment when input is changed with valid datetime */
setMoment: (value: Moment) => void;
/** Boolean denotes whether current input value is valid date */
valid: boolean;
/** Function invoked with value validity when input is changed */
setValid: (valid: boolean) => void;
}
export const DatetimeInput: FunctionComponent<Props> = ({
strValue,
setStrValue,
setMoment,
valid,
setValid,
}) => {
function check(e: ChangeEvent<HTMLInputElement>) {
const parsed = moment(e.target.value, 'YYYY-MM-DD HH:mm:ss', true);
if (parsed.isValid()) {
setMoment(parsed);

View file

@ -1,22 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { compose, withState, lifecycle } from 'recompose';
import { DatetimeInput as Component } from './datetime_input';
export const DatetimeInput = compose(
withState('valid', 'setValid', () => true),
withState('strValue', 'setStrValue', ({ moment }) => moment.format('YYYY-MM-DD HH:mm:ss')),
lifecycle({
componentWillReceiveProps({ moment, setStrValue, setValid }) {
if (this.props.moment.isSame(moment)) {
return;
}
setStrValue(moment.format('YYYY-MM-DD HH:mm:ss'));
setValid(true);
},
})
)(Component);

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { Moment } from 'moment';
import { compose, withState, lifecycle } from 'recompose';
import { DatetimeInput as Component, Props as ComponentProps } from './datetime_input';
export interface Props {
/** Input value (Moment date object) */
moment?: Moment;
/** Function to invoke when the input changes */
setMoment: (m: Moment) => void;
}
export const DatetimeInput = compose<ComponentProps, Props>(
withState('valid', 'setValid', () => true),
withState('strValue', 'setStrValue', ({ moment }) =>
moment ? moment.format('YYYY-MM-DD HH:mm:ss') : ''
),
lifecycle<Props & ComponentProps, {}>({
// TODO: Refactor to no longer use componentWillReceiveProps since it is being deprecated
componentWillReceiveProps({ moment, setStrValue, setValid }) {
if (!moment) return;
if (this.props.moment && this.props.moment.isSame(moment)) {
return;
}
setStrValue(moment.format('YYYY-MM-DD HH:mm:ss'));
setValid(true);
},
})
)(Component);

View file

@ -0,0 +1,219 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots renderers/TimeFilter/components/DatetimeQuickList with children 1`] = `
<div
style={
Object {
"alignItems": "center",
"display": "grid",
}
}
>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 24 hours
</span>
</span>
</button>
<button
className="euiButton euiButton--primary euiButton--small euiButton--fill"
onClick={[Function]}
type="button"
>
<span
className="euiButton__content"
>
<span
className="euiButton__text"
>
Last 7 days
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 2 weeks
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 30 days
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 90 days
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 1 year
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary"
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Apply
</span>
</span>
</button>
</div>
`;
exports[`Storyshots renderers/TimeFilter/components/DatetimeQuickList with start and end dates 1`] = `
<div
style={
Object {
"alignItems": "center",
"display": "grid",
}
}
>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 24 hours
</span>
</span>
</button>
<button
className="euiButton euiButton--primary euiButton--small euiButton--fill"
onClick={[Function]}
type="button"
>
<span
className="euiButton__content"
>
<span
className="euiButton__text"
>
Last 7 days
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 2 weeks
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 30 days
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 90 days
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 1 year
</span>
</span>
</button>
</div>
`;

View file

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiButtonEmpty } from '@elastic/eui';
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import React from 'react';
import { DatetimeQuickList } from '..';
storiesOf('renderers/TimeFilter/components/DatetimeQuickList', module)
.add('with start and end dates', () => (
<DatetimeQuickList from="now-7d" to="now" onSelect={action('onSelect')} />
))
.add('with children', () => (
<DatetimeQuickList from="now-7d" to="now" onSelect={action('onSelect')}>
<EuiButtonEmpty>Apply</EuiButtonEmpty>
</DatetimeQuickList>
));

View file

@ -1,35 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { EuiButton, EuiButtonEmpty } from '@elastic/eui';
import 'react-datetime/css/react-datetime.css';
export const DatetimeQuickList = ({ from, to, ranges, onSelect, children }) => (
<div style={{ display: 'grid', alignItems: 'center' }}>
{ranges.map((range, i) =>
from === range.from && to === range.to ? (
<EuiButton size="s" fill key={i} onClick={() => onSelect(range.from, range.to)}>
{range.display}
</EuiButton>
) : (
<EuiButtonEmpty size="s" key={i} onClick={() => onSelect(range.from, range.to)}>
{range.display}
</EuiButtonEmpty>
)
)}
{children}
</div>
);
DatetimeQuickList.propTypes = {
from: PropTypes.string,
to: PropTypes.string,
ranges: PropTypes.array,
onSelect: PropTypes.func,
children: PropTypes.node,
};

View file

@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { ReactNode, FunctionComponent } from 'react';
import PropTypes from 'prop-types';
import { EuiButton, EuiButtonEmpty } from '@elastic/eui';
import 'react-datetime/css/react-datetime.css';
interface Props {
/** Initial start date string */
from: string;
/** Initial end date string */
to: string;
/** Function invoked when a date range is clicked */
onSelect: (from: string, to: string) => void;
/** Nodes to display under the date range buttons */
children?: ReactNode;
}
const quickRanges = [
{ from: 'now-24h', to: 'now', display: 'Last 24 hours' },
{ from: 'now-7d', to: 'now', display: 'Last 7 days' },
{ from: 'now-14d', to: 'now', display: 'Last 2 weeks' },
{ from: 'now-30d', to: 'now', display: 'Last 30 days' },
{ from: 'now-90d', to: 'now', display: 'Last 90 days' },
{ from: 'now-1y', to: 'now', display: 'Last 1 year' },
];
export const DatetimeQuickList: FunctionComponent<Props> = ({ from, to, onSelect, children }) => (
<div style={{ display: 'grid', alignItems: 'center' }}>
{quickRanges.map((range, i) =>
from === range.from && to === range.to ? (
<EuiButton size="s" fill key={i} onClick={() => onSelect(range.from, range.to)}>
{range.display}
</EuiButton>
) : (
<EuiButtonEmpty size="s" key={i} onClick={() => onSelect(range.from, range.to)}>
{range.display}
</EuiButtonEmpty>
)
)}
{children}
</div>
);
DatetimeQuickList.propTypes = {
from: PropTypes.string.isRequired,
to: PropTypes.string.isRequired,
onSelect: PropTypes.func.isRequired,
children: PropTypes.node,
};

View file

@ -4,7 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { compose } from 'recompose';
import { TimePickerMini as Component } from './time_picker_mini';
export const TimePickerMini = compose()(Component);
export { DatetimeQuickList } from './datetime_quick_list';

View file

@ -0,0 +1,122 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots renderers/TimeFilter/components/DatetimeRangeAbsolute default 1`] = `
<div
className="canvasDateTimeRangeAbsolute"
>
<div>
<div
className="canvasDateTimeCal"
>
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
style={
Object {
"textAlign": "center",
}
}
type="text"
value="2019-06-21 00:00:00"
/>
</div>
</div>
<span>
<span
className="euiDatePicker euiDatePicker--inline"
>
<div
className="euiFormControlLayout"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<ReactDatePicker
accessibleMode={true}
adjustDateOnChange={true}
className="euiDatePicker euiFieldText"
dateFormat="MM/DD/YYYY hh:mm A"
endDate={"2019-07-05T00:00:00.000Z"}
inline={true}
maxDate={"2019-07-05T00:00:00.000Z"}
onChange={[Function]}
selected={"2019-06-21T00:00:00.000Z"}
shouldCloseOnSelect={false}
showMonthDropdown={true}
showTimeSelect={true}
showYearDropdown={true}
startDate={"2019-06-21T00:00:00.000Z"}
timeFormat="hh:mm A"
yearDropdownItemNumber={7}
/>
</div>
</div>
</span>
</span>
</div>
</div>
<div>
<div
className="canvasDateTimeCal"
>
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
style={
Object {
"textAlign": "center",
}
}
type="text"
value="2019-07-05 00:00:00"
/>
</div>
</div>
<span>
<span
className="euiDatePicker euiDatePicker--inline"
>
<div
className="euiFormControlLayout"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<ReactDatePicker
accessibleMode={true}
adjustDateOnChange={true}
className="euiDatePicker euiFieldText"
dateFormat="MM/DD/YYYY hh:mm A"
endDate={"2019-07-05T00:00:00.000Z"}
inline={true}
minDate={"2019-06-21T00:00:00.000Z"}
onChange={[Function]}
selected={"2019-07-05T00:00:00.000Z"}
shouldCloseOnSelect={false}
showMonthDropdown={true}
showTimeSelect={true}
showYearDropdown={true}
startDate={"2019-06-21T00:00:00.000Z"}
timeFormat="hh:mm A"
yearDropdownItemNumber={7}
/>
</div>
</div>
</span>
</span>
</div>
</div>
</div>
`;

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import moment from 'moment';
import React from 'react';
import { DatetimeRangeAbsolute } from '..';
const startDate = moment.utc('2019-06-21');
const endDate = moment.utc('2019-07-05');
storiesOf('renderers/TimeFilter/components/DatetimeRangeAbsolute', module).add('default', () => (
<DatetimeRangeAbsolute from={startDate} to={endDate} onSelect={action('onSelect')} />
));

View file

@ -4,12 +4,23 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import React, { FunctionComponent } from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { Moment } from 'moment';
import { momentObj } from 'react-moment-proptypes';
import { DatetimeCalendar } from '../datetime_calendar';
export const DatetimeRangeAbsolute = ({ from, to, onSelect }) => (
interface Props {
/** Optional initial start date moment */
from?: Moment;
/** Optional initial end date moment */
to?: Moment;
/** Function invoked when a date is selected from the datetime calendar */
onSelect: (from?: Moment, to?: Moment) => void;
}
export const DatetimeRangeAbsolute: FunctionComponent<Props> = ({ from, to, onSelect }) => (
<div className="canvasDateTimeRangeAbsolute">
<div>
<DatetimeCalendar
@ -19,8 +30,12 @@ export const DatetimeRangeAbsolute = ({ from, to, onSelect }) => (
maxDate={to}
onValueChange={val => onSelect(val, to)}
onSelect={val => {
if (!val || !from) {
return;
}
// sets the time to start of day if only the date was selected
if (moment(from).format('hh:mm:ss a') === val.format('hh:mm:ss a')) {
if (from.format('hh:mm:ss a') === val.format('hh:mm:ss a')) {
onSelect(val.startOf('day'), to);
} else {
onSelect(val, to);
@ -36,9 +51,13 @@ export const DatetimeRangeAbsolute = ({ from, to, onSelect }) => (
minDate={from}
onValueChange={val => onSelect(from, val)}
onSelect={val => {
if (!val || !to) {
return;
}
// set the time to end of day if only the date was selected
if (moment(to).format('hh:mm:ss a') === val.format('hh:mm:ss a')) {
onSelect(from, moment(val).endOf('day'));
if (to.format('hh:mm:ss a') === val.format('hh:mm:ss a')) {
onSelect(from, val.endOf('day'));
} else {
onSelect(from, val);
}
@ -49,7 +68,7 @@ export const DatetimeRangeAbsolute = ({ from, to, onSelect }) => (
);
DatetimeRangeAbsolute.propTypes = {
from: PropTypes.object, // a moment
to: PropTypes.object, // a moment
onSelect: PropTypes.func,
from: momentObj,
to: momentObj,
onSelect: PropTypes.func.isRequired,
};

View file

@ -1,10 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { compose } from 'recompose';
import { DatetimeRangeAbsolute as Component } from './datetime_range_absolute';
export const DatetimeRangeAbsolute = compose()(Component);

View file

@ -4,7 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { compose } from 'recompose';
import { DatetimeCalendar as Component } from './datetime_calendar';
export const DatetimeCalendar = compose()(Component);
export { DatetimeRangeAbsolute } from './datetime_range_absolute';

View file

@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots renderers/TimeFilter/components/PrettyDuration with absolute dates 1`] = `
<span>
~ 6 months ago to ~ 5 months ago
</span>
`;
exports[`Storyshots renderers/TimeFilter/components/PrettyDuration with relative dates 1`] = `
<span>
Last 7 days
</span>
`;

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { storiesOf } from '@storybook/react';
import React from 'react';
import { PrettyDuration } from '..';
storiesOf('renderers/TimeFilter/components/PrettyDuration', module)
.add('with relative dates', () => <PrettyDuration from="now-7d" to="now" />)
.add('with absolute dates', () => <PrettyDuration from="01/01/2019" to="02/01/2019" />);

View file

@ -5,16 +5,16 @@
*/
import dateMath from '@elastic/datemath';
import moment from 'moment';
import { quickRanges } from './quick_ranges';
import { timeUnits } from './time_units';
import moment, { Moment } from 'moment';
import { quickRanges, QuickRange } from './quick_ranges';
import { timeUnits, TimeUnit } from '../../../../../../common/lib/time_units';
const lookupByRange = {};
quickRanges.forEach(function(frame) {
lookupByRange[frame.from + ' to ' + frame.to] = frame;
const lookupByRange: { [key: string]: QuickRange } = {};
quickRanges.forEach(frame => {
lookupByRange[`${frame.from} to ${frame.to}`] = frame;
});
function formatTime(time, roundUp = false) {
function formatTime(time: string | Moment, roundUp = false) {
if (moment.isMoment(time)) {
return time.format('lll');
} else {
@ -27,24 +27,24 @@ function formatTime(time, roundUp = false) {
}
}
function cantLookup(from, to) {
function cantLookup(from: string, to: string) {
return `${formatTime(from)} to ${formatTime(to)}`;
}
export function formatDuration(from, to) {
let text;
export function formatDuration(from: string, to: string) {
// If both parts are date math, try to look up a reasonable string
if (from && to && !moment.isMoment(from) && !moment.isMoment(to)) {
const tryLookup = lookupByRange[from.toString() + ' to ' + to.toString()];
const tryLookup = lookupByRange[`${from.toString()} to ${to.toString()}`];
if (tryLookup) {
return tryLookup.display;
} else {
const fromParts = from.toString().split('-');
if (to.toString() === 'now' && fromParts[0] === 'now' && fromParts[1]) {
const rounded = fromParts[1].split('/');
text = 'Last ' + rounded[0];
let text = `Last ${rounded[0]}`;
if (rounded[1]) {
text = text + ' rounded to the ' + timeUnits[rounded[1]];
const unit = rounded[1] as TimeUnit;
text = `${text} rounded to the ${timeUnits[unit]}`;
}
return text;

View file

@ -1,39 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export const quickRanges = [
{ from: 'now/d', to: 'now/d', display: 'Today', section: 0 },
{ from: 'now/w', to: 'now/w', display: 'This week', section: 0 },
{ from: 'now/M', to: 'now/M', display: 'This month', section: 0 },
{ from: 'now/y', to: 'now/y', display: 'This year', section: 0 },
{ from: 'now/d', to: 'now', display: 'The day so far', section: 0 },
{ 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-1d/d', to: 'now-1d/d', display: 'Yesterday', section: 1 },
{ from: 'now-2d/d', to: 'now-2d/d', display: 'Day before yesterday', section: 1 },
{ from: 'now-7d/d', to: 'now-7d/d', display: 'This day last week', section: 1 },
{ from: 'now-1w/w', to: 'now-1w/w', display: 'Previous week', section: 1 },
{ from: 'now-1M/M', to: 'now-1M/M', display: 'Previous month', section: 1 },
{ from: 'now-1y/y', to: 'now-1y/y', display: 'Previous year', section: 1 },
{ from: 'now-15m', to: 'now', display: 'Last 15 minutes', section: 2 },
{ from: 'now-30m', to: 'now', display: 'Last 30 minutes', section: 2 },
{ from: 'now-1h', to: 'now', display: 'Last 1 hour', section: 2 },
{ from: 'now-4h', to: 'now', display: 'Last 4 hours', section: 2 },
{ from: 'now-12h', to: 'now', display: 'Last 12 hours', section: 2 },
{ from: 'now-24h', to: 'now', display: 'Last 24 hours', section: 2 },
{ from: 'now-7d', to: 'now', display: 'Last 7 days', section: 2 },
{ from: 'now-30d', to: 'now', display: 'Last 30 days', section: 3 },
{ from: 'now-60d', to: 'now', display: 'Last 60 days', section: 3 },
{ from: 'now-90d', to: 'now', display: 'Last 90 days', section: 3 },
{ from: 'now-6M', to: 'now', display: 'Last 6 months', section: 3 },
{ from: 'now-1y', to: 'now', display: 'Last 1 year', section: 3 },
{ from: 'now-2y', to: 'now', display: 'Last 2 years', section: 3 },
{ from: 'now-5y', to: 'now', display: 'Last 5 years', section: 3 },
];

View file

@ -0,0 +1,48 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export interface QuickRange {
/** Start date string of range */
from: string;
/** Start date string of range */
to: string;
/** Display name describing date range */
display: string;
}
export const quickRanges: QuickRange[] = [
{ from: 'now/d', to: 'now/d', display: 'Today' },
{ from: 'now/w', to: 'now/w', display: 'This week' },
{ from: 'now/M', to: 'now/M', display: 'This month' },
{ from: 'now/y', to: 'now/y', display: 'This year' },
{ from: 'now/d', to: 'now', display: 'The day so far' },
{ from: 'now/w', to: 'now', display: 'Week to date' },
{ from: 'now/M', to: 'now', display: 'Month to date' },
{ from: 'now/y', to: 'now', display: 'Year to date' },
{ from: 'now-1d/d', to: 'now-1d/d', display: 'Yesterday' },
{ from: 'now-2d/d', to: 'now-2d/d', display: 'Day before yesterday' },
{ from: 'now-7d/d', to: 'now-7d/d', display: 'This day last week' },
{ from: 'now-1w/w', to: 'now-1w/w', display: 'Previous week' },
{ from: 'now-1M/M', to: 'now-1M/M', display: 'Previous month' },
{ from: 'now-1y/y', to: 'now-1y/y', display: 'Previous year' },
{ from: 'now-15m', to: 'now', display: 'Last 15 minutes' },
{ from: 'now-30m', to: 'now', display: 'Last 30 minutes' },
{ from: 'now-1h', to: 'now', display: 'Last 1 hour' },
{ from: 'now-4h', to: 'now', display: 'Last 4 hours' },
{ from: 'now-12h', to: 'now', display: 'Last 12 hours' },
{ from: 'now-24h', to: 'now', display: 'Last 24 hours' },
{ from: 'now-7d', to: 'now', display: 'Last 7 days' },
{ from: 'now-30d', to: 'now', display: 'Last 30 days' },
{ from: 'now-60d', to: 'now', display: 'Last 60 days' },
{ from: 'now-90d', to: 'now', display: 'Last 90 days' },
{ from: 'now-6M', to: 'now', display: 'Last 6 months' },
{ from: 'now-1y', to: 'now', display: 'Last 1 year' },
{ from: 'now-2y', to: 'now', display: 'Last 2 years' },
{ from: 'now-5y', to: 'now', display: 'Last 5 years' },
];

View file

@ -1,16 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { PropTypes } from 'prop-types';
import { formatDuration } from './lib/format_duration';
export const PrettyDuration = ({ from, to }) => <span>{formatDuration(from, to)}</span>;
PrettyDuration.propTypes = {
from: PropTypes.any.isRequired,
to: PropTypes.any.isRequired,
};

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { FunctionComponent } from 'react';
import PropTypes from 'prop-types';
import { formatDuration } from './lib/format_duration';
interface Props {
/** Initial start date string */
from: string;
/** Initial end date string */
to: string;
}
export const PrettyDuration: FunctionComponent<Props> = ({ from, to }) => (
<span>{formatDuration(from, to)}</span>
);
PrettyDuration.propTypes = {
from: PropTypes.string.isRequired,
to: PropTypes.string.isRequired,
};

View file

@ -0,0 +1,267 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots renderers/TimeFilter compact mode 1`] = `
<div
className="euiPopover euiPopover--anchorDownCenter canvasTimePickerPopover"
container={null}
id="timefilter-popover-trigger-click"
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
>
<div
className="euiPopover__anchor canvasTimePickerPopover__anchor"
>
<button
className="canvasTimePickerPopover__button"
onClick={[Function]}
>
<span>
Last 7 days
</span>
</button>
</div>
</div>
`;
exports[`Storyshots renderers/TimeFilter default 1`] = `
<div
className="canvasTimePicker"
>
<div
className="canvasDateTimeRangeAbsolute"
>
<div>
<div
className="canvasDateTimeCal"
>
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
style={
Object {
"textAlign": "center",
}
}
type="text"
value="2018-06-01 00:00:00"
/>
</div>
</div>
<span>
<span
className="euiDatePicker euiDatePicker--inline"
>
<div
className="euiFormControlLayout"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<ReactDatePicker
accessibleMode={true}
adjustDateOnChange={true}
className="euiDatePicker euiFieldText"
dateFormat="MM/DD/YYYY hh:mm A"
endDate={"2019-05-25T00:00:00.000Z"}
inline={true}
maxDate={"2019-05-25T00:00:00.000Z"}
onChange={[Function]}
selected={"2018-06-01T00:00:00.000Z"}
shouldCloseOnSelect={false}
showMonthDropdown={true}
showTimeSelect={true}
showYearDropdown={true}
startDate={"2018-06-01T00:00:00.000Z"}
timeFormat="hh:mm A"
yearDropdownItemNumber={7}
/>
</div>
</div>
</span>
</span>
</div>
</div>
<div>
<div
className="canvasDateTimeCal"
>
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
style={
Object {
"textAlign": "center",
}
}
type="text"
value="2019-05-25 00:00:00"
/>
</div>
</div>
<span>
<span
className="euiDatePicker euiDatePicker--inline"
>
<div
className="euiFormControlLayout"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<ReactDatePicker
accessibleMode={true}
adjustDateOnChange={true}
className="euiDatePicker euiFieldText"
dateFormat="MM/DD/YYYY hh:mm A"
endDate={"2019-05-25T00:00:00.000Z"}
inline={true}
minDate={"2018-06-01T00:00:00.000Z"}
onChange={[Function]}
selected={"2019-05-25T00:00:00.000Z"}
shouldCloseOnSelect={false}
showMonthDropdown={true}
showTimeSelect={true}
showYearDropdown={true}
startDate={"2018-06-01T00:00:00.000Z"}
timeFormat="hh:mm A"
yearDropdownItemNumber={7}
/>
</div>
</div>
</span>
</span>
</div>
</div>
</div>
<div
style={
Object {
"alignItems": "center",
"display": "grid",
}
}
>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 24 hours
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 7 days
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 2 weeks
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 30 days
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 90 days
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 1 year
</span>
</span>
</button>
<button
className="euiButton euiButton--primary euiButton--small canvasTimePicker__apply euiButton--fill"
disabled={true}
onClick={[Function]}
type="button"
>
<span
className="euiButton__content"
>
<span
className="euiButton__text"
>
Apply
</span>
</span>
</button>
</div>
</div>
`;

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import React from 'react';
import { TimeFilter } from '..';
storiesOf('renderers/TimeFilter', module)
.add('default', () => (
<TimeFilter
filter="timefilter from=now-1y to=now-7d column=@timestamp"
commit={action('commit')}
/>
))
.add('compact mode', () => (
<TimeFilter
filter="timefilter from=now-7d to=now column=@timestamp"
compact
commit={action('commit')}
/>
));

View file

@ -1,40 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { get } from 'lodash';
import { fromExpression } from '@kbn/interpreter/common';
import { TimePicker } from '../time_picker';
import { TimePickerMini } from '../time_picker_mini';
function getFilterMeta(filter) {
const ast = fromExpression(filter);
const column = get(ast, 'chain[0].arguments.column[0]');
const from = get(ast, 'chain[0].arguments.from[0]');
const to = get(ast, 'chain[0].arguments.to[0]');
return { column, from, to };
}
export const TimeFilter = ({ filter, commit, compact }) => {
const setFilter = column => (from, to) => {
commit(`timefilter from="${from}" to=${to} column=${column}`);
};
const { column, from, to } = getFilterMeta(filter);
if (compact) {
return <TimePickerMini from={from} to={to} onSelect={setFilter(column)} />;
} else {
return <TimePicker from={from} to={to} onSelect={setFilter(column)} />;
}
};
TimeFilter.propTypes = {
filter: PropTypes.string,
commit: PropTypes.func, // Canvas filter
compact: PropTypes.bool,
};

View file

@ -0,0 +1,58 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { get } from 'lodash';
import { fromExpression } from '@kbn/interpreter/common';
import { TimePicker } from '../time_picker';
import { TimePickerPopover } from '../time_picker_popover';
export interface FilterMeta {
/** Name of datetime column to be filtered */
column: string;
/** Start date string of filtered date range */
from: string;
/** End date string of filtered date range */
to: string;
}
function getFilterMeta(filter: string): FilterMeta {
const ast = fromExpression(filter);
const column = get<string>(ast, 'chain[0].arguments.column[0]');
const from = get<string>(ast, 'chain[0].arguments.from[0]');
const to = get<string>(ast, 'chain[0].arguments.to[0]');
return { column, from, to };
}
export interface Props {
/** Initial value of the filter */
filter: string;
/** Function invoked when the filter changes */
commit: (filter: string) => void;
/** Determines if compact or full-sized time picker is displayed */
compact?: boolean;
}
export const TimeFilter = ({ filter, commit, compact }: Props) => {
const setFilter = (column: string) => (from: string, to: string) => {
commit(`timefilter from="${from}" to=${to} column=${column}`);
};
const { column, from, to } = getFilterMeta(filter);
if (compact) {
return <TimePickerPopover from={from} to={to} onSelect={setFilter(column)} />;
} else {
return <TimePicker from={from} to={to} onSelect={setFilter(column)} />;
}
};
TimeFilter.propTypes = {
filter: PropTypes.string.isRequired,
commit: PropTypes.func.isRequired, // Canvas filter
compact: PropTypes.bool,
};

View file

@ -0,0 +1,241 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots renderers/TimeFilter/components/TimePicker default 1`] = `
<div
className="canvasTimePicker"
>
<div
className="canvasDateTimeRangeAbsolute"
>
<div>
<div
className="canvasDateTimeCal"
>
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
style={
Object {
"textAlign": "center",
}
}
type="text"
value="2018-04-04 00:00:00"
/>
</div>
</div>
<span>
<span
className="euiDatePicker euiDatePicker--inline"
>
<div
className="euiFormControlLayout"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<ReactDatePicker
accessibleMode={true}
adjustDateOnChange={true}
className="euiDatePicker euiFieldText"
dateFormat="MM/DD/YYYY hh:mm A"
endDate={"2019-04-04T00:00:00.000Z"}
inline={true}
maxDate={"2019-04-04T00:00:00.000Z"}
onChange={[Function]}
selected={"2018-04-04T00:00:00.000Z"}
shouldCloseOnSelect={false}
showMonthDropdown={true}
showTimeSelect={true}
showYearDropdown={true}
startDate={"2018-04-04T00:00:00.000Z"}
timeFormat="hh:mm A"
yearDropdownItemNumber={7}
/>
</div>
</div>
</span>
</span>
</div>
</div>
<div>
<div
className="canvasDateTimeCal"
>
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
style={
Object {
"textAlign": "center",
}
}
type="text"
value="2019-04-04 00:00:00"
/>
</div>
</div>
<span>
<span
className="euiDatePicker euiDatePicker--inline"
>
<div
className="euiFormControlLayout"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<ReactDatePicker
accessibleMode={true}
adjustDateOnChange={true}
className="euiDatePicker euiFieldText"
dateFormat="MM/DD/YYYY hh:mm A"
endDate={"2019-04-04T00:00:00.000Z"}
inline={true}
minDate={"2018-04-04T00:00:00.000Z"}
onChange={[Function]}
selected={"2019-04-04T00:00:00.000Z"}
shouldCloseOnSelect={false}
showMonthDropdown={true}
showTimeSelect={true}
showYearDropdown={true}
startDate={"2018-04-04T00:00:00.000Z"}
timeFormat="hh:mm A"
yearDropdownItemNumber={7}
/>
</div>
</div>
</span>
</span>
</div>
</div>
</div>
<div
style={
Object {
"alignItems": "center",
"display": "grid",
}
}
>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 24 hours
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 7 days
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 2 weeks
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 30 days
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 90 days
</span>
</span>
</button>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small"
onClick={[Function]}
type="button"
>
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__text"
>
Last 1 year
</span>
</span>
</button>
<button
className="euiButton euiButton--primary euiButton--small canvasTimePicker__apply euiButton--fill"
disabled={true}
onClick={[Function]}
type="button"
>
<span
className="euiButton__content"
>
<span
className="euiButton__text"
>
Apply
</span>
</span>
</button>
</div>
</div>
`;

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import moment from 'moment';
import React from 'react';
import { TimePicker } from '..';
const startDate = moment.utc('2018-04-04').toISOString();
const endDate = moment.utc('2019-04-04').toISOString();
storiesOf('renderers/TimeFilter/components/TimePicker', module).add('default', () => (
<TimePicker from={startDate} to={endDate} onSelect={action('onSelect')} />
));

View file

@ -1,21 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { compose, withState, lifecycle } from 'recompose';
import { TimePicker as Component } from './time_picker';
export const TimePicker = compose(
withState('range', 'setRange', ({ from, to }) => ({ from, to })),
withState('dirty', 'setDirty', false),
lifecycle({
componentWillReceiveProps({ from, to }) {
if (from !== this.props.from || to !== this.props.to) {
this.props.setRange({ from, to });
this.props.setDirty(false);
}
},
})
)(Component);

View file

@ -4,7 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { compose } from 'recompose';
import { DatetimeQuickList as Component } from './datetime_quick_list';
export const DatetimeQuickList = compose()(Component);
export { TimePicker } from './time_picker';

View file

@ -1,64 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import dateMath from '@elastic/datemath';
import { EuiButton } from '@elastic/eui';
import moment from 'moment';
import { DatetimeRangeAbsolute } from '../datetime_range_absolute';
import { DatetimeQuickList } from '../datetime_quick_list';
export const quickRanges = [
{ from: 'now/d', to: 'now', display: 'Today' },
{ from: 'now-24h', to: 'now', display: 'Last 24 hours' },
{ from: 'now-7d', to: 'now', display: 'Last 7 days' },
{ from: 'now-14d', to: 'now', display: 'Last 2 weeks' },
{ from: 'now-30d', to: 'now', display: 'Last 30 days' },
{ from: 'now-90d', to: 'now', display: 'Last 90 days' },
{ from: 'now-1y', to: 'now', display: 'Last 1 year' },
];
export const TimePicker = ({ range, setRange, dirty, setDirty, onSelect }) => {
const { from, to } = range;
function absoluteSelect(from, to) {
setDirty(true);
setRange({ from: moment(from).toISOString(), to: moment(to).toISOString() });
}
return (
<div className="canvasTimePicker">
<DatetimeRangeAbsolute
from={dateMath.parse(from)}
to={dateMath.parse(to)}
onSelect={absoluteSelect}
/>
<DatetimeQuickList from={range.from} to={range.to} ranges={quickRanges} onSelect={onSelect}>
<EuiButton
fill
size="s"
disabled={!dirty}
className="canvasTimePicker__apply"
onClick={() => {
setDirty(false);
onSelect(range.from, range.to);
}}
>
Apply
</EuiButton>
</DatetimeQuickList>
</div>
);
};
TimePicker.propTypes = {
range: PropTypes.object,
setRange: PropTypes.func,
dirty: PropTypes.bool,
setDirty: PropTypes.func,
onSelect: PropTypes.func,
};

View file

@ -0,0 +1,95 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import dateMath from '@elastic/datemath';
import { EuiButton } from '@elastic/eui';
import moment from 'moment';
import { DatetimeQuickList } from '../datetime_quick_list';
import { DatetimeRangeAbsolute } from '../datetime_range_absolute';
export interface Props {
/** Start date string */
from: string;
/** End date string */
to: string;
/** Function invoked when date range is changed */
onSelect: (from: string, to: string) => void;
}
export interface State {
range: {
/** Start date string of selected date range */
from: string;
/** End date string of selected date range */
to: string;
};
/** Boolean denoting whether selected date range has been applied */
isDirty: boolean;
}
export class TimePicker extends Component<Props, State> {
static propTypes = {
from: PropTypes.string.isRequired,
to: PropTypes.string.isRequired,
onSelect: PropTypes.func.isRequired,
};
state = {
range: { from: this.props.from, to: this.props.to },
isDirty: false,
};
// TODO: Refactor to no longer use componentWillReceiveProps since it is being deprecated
componentWillReceiveProps({ from, to }: Props) {
if (from !== this.props.from || to !== this.props.to) {
this.setState({
range: { from, to },
isDirty: false,
});
}
}
_absoluteSelect = (from?: moment.Moment, to?: moment.Moment) => {
if (from && to) {
this.setState({
range: { from: moment(from).toISOString(), to: moment(to).toISOString() },
isDirty: true,
});
}
};
render() {
const { onSelect } = this.props;
const { range, isDirty } = this.state;
const { from, to } = range;
return (
<div className="canvasTimePicker">
<DatetimeRangeAbsolute
from={dateMath.parse(from)}
to={dateMath.parse(to)}
onSelect={this._absoluteSelect}
/>
<DatetimeQuickList from={from} to={to} onSelect={onSelect}>
<EuiButton
fill
size="s"
disabled={!isDirty}
className="canvasTimePicker__apply"
onClick={() => {
this.setState({ isDirty: false });
onSelect(from, to);
}}
>
Apply
</EuiButton>
</DatetimeQuickList>
</div>
);
}
}

View file

@ -1,36 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Popover } from '../../../../../public/components/popover';
import { PrettyDuration } from '../pretty_duration';
import { TimePicker } from '../time_picker';
export const TimePickerMini = ({ from, to, onSelect }) => {
const button = handleClick => (
<button className="canvasTimePickerMini__button" onClick={handleClick}>
<PrettyDuration from={from} to={to} />
</button>
);
return (
<Popover
id="timefilter-popover-trigger-click"
className="canvasTimePickerMini"
anchorClassName="canvasTimePickerMini__anchor"
button={button}
>
{() => <TimePicker from={from} to={to} onSelect={onSelect} />}
</Popover>
);
};
TimePickerMini.propTypes = {
from: PropTypes.string,
to: PropTypes.string,
onSelect: PropTypes.func,
};

View file

@ -0,0 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots renderers/TimeFilter/components/TimePickerPopover default 1`] = `
<div
className="euiPopover euiPopover--anchorDownCenter canvasTimePickerPopover"
container={null}
id="timefilter-popover-trigger-click"
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
>
<div
className="euiPopover__anchor canvasTimePickerPopover__anchor"
>
<button
className="canvasTimePickerPopover__button"
onClick={[Function]}
>
<span>
~ 2 months ago to ~ a month ago
</span>
</button>
</div>
</div>
`;

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import moment from 'moment';
import React from 'react';
import { TimePickerPopover } from '..';
const startDate = moment.utc('2019-05-04').toISOString();
const endDate = moment.utc('2019-06-04').toISOString();
storiesOf('renderers/TimeFilter/components/TimePickerPopover', module).add('default', () => (
<TimePickerPopover from={startDate} to={endDate} onSelect={action('onSelect')} />
));

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { TimePickerPopover } from './time_picker_popover';

View file

@ -1,7 +1,7 @@
.canvasTimePickerMini {
.canvasTimePickerPopover {
width: 100%;
.canvasTimePickerMini__button {
.canvasTimePickerPopover__button {
width: 100%;
padding: $euiSizeXS;
border: $euiBorderThin;
@ -13,7 +13,7 @@
}
}
.canvasTimePickerMini__anchor {
.canvasTimePickerPopover__anchor {
width: 100%;
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { FunctionComponent, MouseEvent } from 'react';
import PropTypes from 'prop-types';
// @ts-ignore untyped local
import { Popover } from '../../../../../public/components/popover';
import { PrettyDuration } from '../pretty_duration';
import { TimePicker } from '../time_picker';
export interface Props {
/** Start date string */
from: string;
/** End date string */
to: string;
/** Function invoked when date range is changed */
onSelect: (from: string, to: string) => void;
}
export const TimePickerPopover: FunctionComponent<Props> = ({ from, to, onSelect }) => {
const button = (handleClick: (event: MouseEvent<HTMLButtonElement>) => void) => (
<button className="canvasTimePickerPopover__button" onClick={handleClick}>
<PrettyDuration from={from} to={to} />
</button>
);
return (
<Popover
id="timefilter-popover-trigger-click"
className="canvasTimePickerPopover"
anchorClassName="canvasTimePickerPopover__anchor"
button={button}
>
{() => <TimePicker from={from} to={to} onSelect={onSelect} />}
</Popover>
);
};
TimePickerPopover.propTypes = {
from: PropTypes.string.isRequired,
to: PropTypes.string.isRequired,
onSelect: PropTypes.func.isRequired,
};

View file

@ -289,7 +289,7 @@
"angle": 0,
"parent": null
},
"expression": "timefilterControl compact=true column=\"@timestamp\"\n| render css=\".canvasTimePickerMini__button {\n border: none !important;\n}\"",
"expression": "timefilterControl compact=true column=\"@timestamp\"\n| render css=\".canvasTimePickerPopover__button {\n border: none !important;\n}\"",
"filter": "timefilter from=\"now-14d\" to=now column=@timestamp"
},
{

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export enum TimeUnit {
SECONDS = 's',
MINUTES = 'm',
HOURS = 'h',
DAYS = 'd',
WEEKS = 'w',
MONTHS = 'M',
YEARS = 'y',
}
export const timeUnits: Record<TimeUnit, string> = {
[TimeUnit.SECONDS]: 'second',
[TimeUnit.MINUTES]: 'minute',
[TimeUnit.HOURS]: 'hour',
[TimeUnit.DAYS]: 'day',
[TimeUnit.WEEKS]: 'week',
[TimeUnit.MONTHS]: 'month',
[TimeUnit.YEARS]: 'year',
};

View file

@ -67,5 +67,5 @@
@import '../../canvas_plugin_src/renderers/time_filter/components/datetime_calendar/datetime_calendar.scss';
@import '../../canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/datetime_range_absolute.scss';
@import '../../canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.scss';
@import '../../canvas_plugin_src/renderers/time_filter/components/time_picker_mini/time_picker_mini.scss';
@import '../../canvas_plugin_src/renderers/time_filter/components/time_picker_popover/time_picker_popover.scss';
@import '../../canvas_plugin_src/uis/arguments/image_upload/image_upload.scss';

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
declare module 'react-moment-proptypes';

View file

@ -306,6 +306,7 @@
"react-fast-compare": "^2.0.4",
"react-markdown": "^3.4.1",
"react-markdown-renderer": "^1.4.0",
"react-moment-proptypes": "^1.6.0",
"react-portal": "^3.2.0",
"react-redux": "^5.0.7",
"react-redux-request": "^1.5.6",

View file

@ -19639,7 +19639,7 @@ moment@2.22.2, moment@>=2.14.0:
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=
moment@2.24.0:
moment@2.24.0, moment@>=1.6.0:
version "2.24.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
@ -23139,6 +23139,13 @@ react-modal@^3.8.1:
react-lifecycles-compat "^3.0.0"
warning "^3.0.0"
react-moment-proptypes@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/react-moment-proptypes/-/react-moment-proptypes-1.6.0.tgz#8ec266ee392a08ba3412d2df2eebf833ab1046df"
integrity sha512-4h7EuhDMTzQqZ+02KUUO+AVA7PqhbD88yXB740nFpNDyDS/bj9jiPyn2rwr9sa8oDyaE1ByFN9+t5XPyPTmN6g==
dependencies:
moment ">=1.6.0"
react-motion@^0.4.8:
version "0.4.8"
resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.4.8.tgz#23bb2dd27c2d8e00d229e45572d105efcf40a35e"