mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Grafana-UI: Enhances for TimeRangePicker and TimeRangeInput (#30102)
This commit is contained in:
parent
9734b7069c
commit
46167785e6
@ -39,6 +39,7 @@
|
|||||||
"@types/react-color": "3.0.1",
|
"@types/react-color": "3.0.1",
|
||||||
"@types/react-select": "3.0.8",
|
"@types/react-select": "3.0.8",
|
||||||
"@types/react-table": "7.0.12",
|
"@types/react-table": "7.0.12",
|
||||||
|
"@testing-library/jest-dom": "5.11.9",
|
||||||
"@sentry/browser": "5.25.0",
|
"@sentry/browser": "5.25.0",
|
||||||
"@types/slate": "0.47.1",
|
"@types/slate": "0.47.1",
|
||||||
"@types/slate-react": "0.22.5",
|
"@types/slate-react": "0.22.5",
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
import { Story } from '@storybook/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
import { dateTime, TimeFragment } from '@grafana/data';
|
import { dateTime, DefaultTimeZone, TimeRange, TimeZone } from '@grafana/data';
|
||||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||||
import { UseState } from '../../utils/storybook/UseState';
|
import { UseState } from '../../utils/storybook/UseState';
|
||||||
import { TimeRangeInput } from '@grafana/ui';
|
import { TimeRangeInput } from '@grafana/ui';
|
||||||
|
import { TimeRangeInputProps } from './TimeRangeInput';
|
||||||
import mdx from './TimeRangeInput.mdx';
|
import mdx from './TimeRangeInput.mdx';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -17,51 +19,75 @@ export default {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const basic = () => {
|
interface State {
|
||||||
return (
|
value: TimeRange;
|
||||||
<UseState
|
timeZone: TimeZone;
|
||||||
initialState={{
|
}
|
||||||
from: dateTime(),
|
|
||||||
to: dateTime(),
|
const getComponentWithState = (initialState: State, props: TimeRangeInputProps) => (
|
||||||
raw: { from: 'now-6h' as TimeFragment, to: 'now' as TimeFragment },
|
<UseState initialState={initialState}>
|
||||||
}}
|
{(state, updateValue) => {
|
||||||
>
|
return (
|
||||||
{(value, updateValue) => {
|
<TimeRangeInput
|
||||||
return (
|
{...props}
|
||||||
<TimeRangeInput
|
value={state.value}
|
||||||
value={value}
|
timeZone={state.timeZone}
|
||||||
onChange={timeRange => {
|
onChange={value => {
|
||||||
action('onChange fired')(timeRange);
|
action('onChange fired')(value);
|
||||||
updateValue(timeRange);
|
updateValue({
|
||||||
}}
|
...state,
|
||||||
/>
|
value,
|
||||||
);
|
});
|
||||||
}}
|
}}
|
||||||
</UseState>
|
onChangeTimeZone={timeZone => {
|
||||||
|
action('onChangeTimeZone fired')(timeZone);
|
||||||
|
updateValue({
|
||||||
|
...state,
|
||||||
|
timeZone,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</UseState>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Relative: Story<TimeRangeInputProps> = props => {
|
||||||
|
const to = dateTime();
|
||||||
|
const from = to.subtract(6, 'h');
|
||||||
|
|
||||||
|
return getComponentWithState(
|
||||||
|
{
|
||||||
|
value: {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
raw: {
|
||||||
|
from: 'now-6h',
|
||||||
|
to: 'now',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timeZone: DefaultTimeZone,
|
||||||
|
},
|
||||||
|
props
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clearable = () => {
|
export const Absolute: Story<TimeRangeInputProps> = props => {
|
||||||
return (
|
const to = dateTime();
|
||||||
<UseState
|
const from = to.subtract(6, 'h');
|
||||||
initialState={{
|
|
||||||
from: dateTime(),
|
return getComponentWithState(
|
||||||
to: dateTime(),
|
{
|
||||||
raw: { from: 'now-6h' as TimeFragment, to: 'now' as TimeFragment },
|
value: {
|
||||||
}}
|
from,
|
||||||
>
|
to,
|
||||||
{(value, updateValue) => {
|
raw: {
|
||||||
return (
|
from,
|
||||||
<TimeRangeInput
|
to,
|
||||||
clearable
|
},
|
||||||
value={value}
|
},
|
||||||
onChange={timeRange => {
|
timeZone: DefaultTimeZone,
|
||||||
action('onChange fired')(timeRange);
|
},
|
||||||
updateValue(timeRange);
|
props
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</UseState>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -14,7 +14,7 @@ const isValidTimeRange = (range: any) => {
|
|||||||
return dateMath.isValid(range.from) && dateMath.isValid(range.to);
|
return dateMath.isValid(range.from) && dateMath.isValid(range.to);
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface Props {
|
export interface TimeRangeInputProps {
|
||||||
value: TimeRange;
|
value: TimeRange;
|
||||||
timeZone?: TimeZone;
|
timeZone?: TimeZone;
|
||||||
onChange: (timeRange: TimeRange) => void;
|
onChange: (timeRange: TimeRange) => void;
|
||||||
@ -22,18 +22,22 @@ export interface Props {
|
|||||||
hideTimeZone?: boolean;
|
hideTimeZone?: boolean;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
clearable?: boolean;
|
clearable?: boolean;
|
||||||
|
isReversed?: boolean;
|
||||||
|
hideQuickRanges?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const noop = () => {};
|
const noop = () => {};
|
||||||
|
|
||||||
export const TimeRangeInput: FC<Props> = ({
|
export const TimeRangeInput: FC<TimeRangeInputProps> = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
onChangeTimeZone,
|
onChangeTimeZone = noop,
|
||||||
clearable,
|
clearable,
|
||||||
hideTimeZone = true,
|
hideTimeZone = true,
|
||||||
timeZone = 'browser',
|
timeZone = 'browser',
|
||||||
placeholder = 'Select time range',
|
placeholder = 'Select time range',
|
||||||
|
isReversed = true,
|
||||||
|
hideQuickRanges = false,
|
||||||
}) => {
|
}) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const styles = useStyles(getStyles);
|
const styles = useStyles(getStyles);
|
||||||
@ -84,10 +88,11 @@ export const TimeRangeInput: FC<Props> = ({
|
|||||||
onChange={onRangeChange}
|
onChange={onRangeChange}
|
||||||
otherOptions={otherOptions}
|
otherOptions={otherOptions}
|
||||||
quickOptions={quickOptions}
|
quickOptions={quickOptions}
|
||||||
onChangeTimeZone={onChangeTimeZone || noop}
|
onChangeTimeZone={onChangeTimeZone}
|
||||||
className={styles.content}
|
className={styles.content}
|
||||||
hideTimeZone={hideTimeZone}
|
hideTimeZone={hideTimeZone}
|
||||||
isReversed
|
isReversed={isReversed}
|
||||||
|
hideQuickRanges={hideQuickRanges}
|
||||||
/>
|
/>
|
||||||
</ClickOutsideWrapper>
|
</ClickOutsideWrapper>
|
||||||
)}
|
)}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
import { Story } from '@storybook/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
import { TimeRangePicker } from '@grafana/ui';
|
import { Button, TimeRangePicker } from '@grafana/ui';
|
||||||
import { UseState } from '../../utils/storybook/UseState';
|
import { UseState } from '../../utils/storybook/UseState';
|
||||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||||
import { TimeFragment, dateTime } from '@grafana/data';
|
import { dateTime, TimeRange, DefaultTimeZone, TimeZone, isDateTime } from '@grafana/data';
|
||||||
|
import { TimeRangePickerProps } from './TimeRangePicker';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pickers and Editors/TimePickers/TimeRangePicker',
|
title: 'Pickers and Editors/TimePickers/TimeRangePicker',
|
||||||
@ -12,24 +14,37 @@ export default {
|
|||||||
decorators: [withCenteredStory],
|
decorators: [withCenteredStory],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const basic = () => {
|
interface State {
|
||||||
return (
|
value: TimeRange;
|
||||||
<UseState
|
timeZone: TimeZone;
|
||||||
initialState={{
|
history: TimeRange[];
|
||||||
from: dateTime(),
|
}
|
||||||
to: dateTime(),
|
|
||||||
raw: { from: 'now-6h' as TimeFragment, to: 'now' as TimeFragment },
|
const getComponentWithState = (initialState: State, props: TimeRangePickerProps) => (
|
||||||
}}
|
<UseState initialState={initialState}>
|
||||||
>
|
{(state, updateValue) => {
|
||||||
{(value, updateValue) => {
|
return (
|
||||||
return (
|
<>
|
||||||
<TimeRangePicker
|
<TimeRangePicker
|
||||||
onChangeTimeZone={() => {}}
|
{...props}
|
||||||
timeZone="browser"
|
timeZone={state.timeZone}
|
||||||
value={value}
|
value={state.value}
|
||||||
onChange={timeRange => {
|
history={state.history}
|
||||||
action('onChange fired')(timeRange);
|
onChange={value => {
|
||||||
updateValue(timeRange);
|
action('onChange fired')(value);
|
||||||
|
updateValue({
|
||||||
|
...state,
|
||||||
|
value,
|
||||||
|
history:
|
||||||
|
isDateTime(value.raw.from) && isDateTime(value.raw.to) ? [...state.history, value] : state.history,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onChangeTimeZone={timeZone => {
|
||||||
|
action('onChangeTimeZone fired')(timeZone);
|
||||||
|
updateValue({
|
||||||
|
...state,
|
||||||
|
timeZone,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
onMoveBackward={() => {
|
onMoveBackward={() => {
|
||||||
action('onMoveBackward fired')();
|
action('onMoveBackward fired')();
|
||||||
@ -41,8 +56,60 @@ export const basic = () => {
|
|||||||
action('onZoom fired')();
|
action('onZoom fired')();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
<Button
|
||||||
}}
|
onClick={() => {
|
||||||
</UseState>
|
updateValue({
|
||||||
|
...state,
|
||||||
|
history: [],
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear history
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</UseState>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Relative: Story<TimeRangePickerProps> = props => {
|
||||||
|
const to = dateTime();
|
||||||
|
const from = to.subtract(6, 'h');
|
||||||
|
|
||||||
|
return getComponentWithState(
|
||||||
|
{
|
||||||
|
value: {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
raw: {
|
||||||
|
from: 'now-6h',
|
||||||
|
to: 'now',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timeZone: DefaultTimeZone,
|
||||||
|
history: [],
|
||||||
|
},
|
||||||
|
props
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Absolute: Story<TimeRangePickerProps> = props => {
|
||||||
|
const to = dateTime();
|
||||||
|
const from = to.subtract(6, 'h');
|
||||||
|
|
||||||
|
return getComponentWithState(
|
||||||
|
{
|
||||||
|
value: {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
raw: {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timeZone: DefaultTimeZone,
|
||||||
|
history: [],
|
||||||
|
},
|
||||||
|
props
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import React from 'react';
|
|
||||||
import { mount } from 'enzyme';
|
|
||||||
import { UnthemedTimeRangePicker } from './TimeRangePicker';
|
|
||||||
import { dateTime, TimeRange } from '@grafana/data';
|
import { dateTime, TimeRange } from '@grafana/data';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
import dark from '../../themes/dark';
|
import dark from '../../themes/dark';
|
||||||
|
import { UnthemedTimeRangePicker } from './TimeRangePicker';
|
||||||
|
|
||||||
const from = '2019-12-17T07:48:27.433Z';
|
const from = dateTime('2019-12-17T07:48:27.433Z');
|
||||||
const to = '2019-12-18T07:48:27.433Z';
|
const to = dateTime('2019-12-18T07:48:27.433Z');
|
||||||
|
|
||||||
const value: TimeRange = {
|
const value: TimeRange = {
|
||||||
from: dateTime(from),
|
from,
|
||||||
to: dateTime(to),
|
to,
|
||||||
raw: { from: dateTime(from), to: dateTime(to) },
|
raw: { from, to },
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('TimePicker', () => {
|
describe('TimePicker', () => {
|
||||||
it('renders buttons correctly', () => {
|
it('renders buttons correctly', () => {
|
||||||
const wrapper = mount(
|
const container = render(
|
||||||
<UnthemedTimeRangePicker
|
<UnthemedTimeRangePicker
|
||||||
onChangeTimeZone={() => {}}
|
onChangeTimeZone={() => {}}
|
||||||
onChange={value => {}}
|
onChange={value => {}}
|
||||||
@ -26,6 +26,7 @@ describe('TimePicker', () => {
|
|||||||
theme={dark}
|
theme={dark}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(wrapper.exists('.navbar-button')).toBe(true);
|
|
||||||
|
expect(container.queryByLabelText(/timepicker open button/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -56,7 +56,7 @@ const getLabelStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface Props extends Themeable {
|
export interface TimeRangePickerProps extends Themeable {
|
||||||
hideText?: boolean;
|
hideText?: boolean;
|
||||||
value: TimeRange;
|
value: TimeRange;
|
||||||
timeZone?: TimeZone;
|
timeZone?: TimeZone;
|
||||||
@ -68,13 +68,14 @@ export interface Props extends Themeable {
|
|||||||
onMoveForward: () => void;
|
onMoveForward: () => void;
|
||||||
onZoom: () => void;
|
onZoom: () => void;
|
||||||
history?: TimeRange[];
|
history?: TimeRange[];
|
||||||
|
hideQuickRanges?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnthemedTimeRangePicker extends PureComponent<Props, State> {
|
export class UnthemedTimeRangePicker extends PureComponent<TimeRangePickerProps, State> {
|
||||||
state: State = {
|
state: State = {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
};
|
};
|
||||||
@ -107,6 +108,7 @@ export class UnthemedTimeRangePicker extends PureComponent<Props, State> {
|
|||||||
theme,
|
theme,
|
||||||
history,
|
history,
|
||||||
onChangeTimeZone,
|
onChangeTimeZone,
|
||||||
|
hideQuickRanges,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { isOpen } = this.state;
|
const { isOpen } = this.state;
|
||||||
@ -146,6 +148,7 @@ export class UnthemedTimeRangePicker extends PureComponent<Props, State> {
|
|||||||
history={history}
|
history={history}
|
||||||
showHistory
|
showHistory
|
||||||
onChangeTimeZone={onChangeTimeZone}
|
onChangeTimeZone={onChangeTimeZone}
|
||||||
|
hideQuickRanges={hideQuickRanges}
|
||||||
/>
|
/>
|
||||||
</ClickOutsideWrapper>
|
</ClickOutsideWrapper>
|
||||||
)}
|
)}
|
||||||
@ -192,7 +195,7 @@ const TimePickerTooltip = ({ timeRange, timeZone }: { timeRange: TimeRange; time
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type LabelProps = Pick<Props, 'hideText' | 'value' | 'timeZone'>;
|
type LabelProps = Pick<TimeRangePickerProps, 'hideText' | 'value' | 'timeZone'>;
|
||||||
|
|
||||||
export const TimePickerButtonLabel = memo<LabelProps>(({ hideText, value, timeZone }) => {
|
export const TimePickerButtonLabel = memo<LabelProps>(({ hideText, value, timeZone }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
@ -1,59 +1,167 @@
|
|||||||
import React from 'react';
|
|
||||||
import { shallow } from 'enzyme';
|
|
||||||
import { TimePickerContentWithScreenSize } from './TimePickerContent';
|
|
||||||
import { dateTime, TimeRange } from '@grafana/data';
|
import { dateTime, TimeRange } from '@grafana/data';
|
||||||
|
import { render, RenderResult, screen } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { PropsWithScreenSize, TimePickerContentWithScreenSize } from './TimePickerContent';
|
||||||
|
|
||||||
describe('TimePickerContent', () => {
|
describe('TimePickerContent', () => {
|
||||||
it('renders correctly in full screen', () => {
|
const absoluteValue = createAbsoluteTimeRange('2019-12-17T07:48:27.433Z', '2019-12-18T07:49:27.433Z');
|
||||||
const value = createTimeRange('2019-12-17T07:48:27.433Z', '2019-12-18T07:48:27.433Z');
|
const relativeValue = createRelativeTimeRange();
|
||||||
const wrapper = shallow(
|
const history = [
|
||||||
<TimePickerContentWithScreenSize
|
createAbsoluteTimeRange('2019-12-17T07:48:27.433Z', '2019-12-17T07:49:27.433Z'),
|
||||||
onChangeTimeZone={() => {}}
|
createAbsoluteTimeRange('2019-10-18T07:50:27.433Z', '2019-10-18T07:51:27.433Z'),
|
||||||
onChange={value => {}}
|
];
|
||||||
timeZone="utc"
|
|
||||||
value={value}
|
describe('Wide Screen', () => {
|
||||||
isFullscreen={true}
|
it('renders with history', () => {
|
||||||
/>
|
renderComponent({ value: absoluteValue, history });
|
||||||
);
|
expect(screen.queryByText(/recently used absolute ranges/i)).toBeInTheDocument();
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(screen.queryByText(/2019-12-17 07:48:27 to 2019-12-17 07:49:27/i)).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/2019-10-18 07:50:27 to 2019-10-18 07:51:27/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with empty history', () => {
|
||||||
|
renderComponent({ value: absoluteValue });
|
||||||
|
expect(screen.queryByText(/recently used absolute ranges/i)).not.toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.queryByText(
|
||||||
|
/it looks like you haven't used this time picker before\. as soon as you enter some time intervals, recently used intervals will appear here\./i
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders without history', () => {
|
||||||
|
renderComponent({ value: absoluteValue, history, showHistory: false });
|
||||||
|
expect(screen.queryByText(/recently used absolute ranges/i)).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/2019-12-17 07:48:27 to 2019-12-17 07:49:27/i)).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/2019-10-18 07:50:27 to 2019-10-18 07:51:27/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with relative picker', () => {
|
||||||
|
renderComponent({ value: absoluteValue });
|
||||||
|
expect(screen.queryByText(/relative time ranges/i)).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/other quick ranges/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders without relative picker', () => {
|
||||||
|
renderComponent({ value: absoluteValue, hideQuickRanges: true });
|
||||||
|
expect(screen.queryByText(/relative time ranges/i)).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/other quick ranges/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with timezone picker', () => {
|
||||||
|
renderComponent({ value: absoluteValue, hideTimeZone: false });
|
||||||
|
expect(screen.queryByText(/coordinated universal time/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders without timezone picker', () => {
|
||||||
|
renderComponent({ value: absoluteValue, hideTimeZone: true });
|
||||||
|
expect(screen.queryByText(/coordinated universal time/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correctly in narrow screen', () => {
|
describe('Narrow Screen', () => {
|
||||||
const value = createTimeRange('2019-12-17T07:48:27.433Z', '2019-12-18T07:48:27.433Z');
|
it('renders with history', () => {
|
||||||
const wrapper = shallow(
|
renderComponent({ value: absoluteValue, history, isFullscreen: false });
|
||||||
<TimePickerContentWithScreenSize
|
expect(screen.queryByText(/recently used absolute ranges/i)).toBeInTheDocument();
|
||||||
onChangeTimeZone={() => {}}
|
expect(screen.queryByText(/2019-12-17 07:48:27 to 2019-12-17 07:49:27/i)).toBeInTheDocument();
|
||||||
onChange={value => {}}
|
expect(screen.queryByText(/2019-10-18 07:50:27 to 2019-10-18 07:51:27/i)).toBeInTheDocument();
|
||||||
timeZone="utc"
|
});
|
||||||
value={value}
|
|
||||||
isFullscreen={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders recent absolute ranges correctly', () => {
|
it('renders with empty history', () => {
|
||||||
const value = createTimeRange('2019-12-17T07:48:27.433Z', '2019-12-18T07:48:27.433Z');
|
renderComponent({ value: absoluteValue, isFullscreen: false });
|
||||||
const history = [
|
expect(screen.queryByText(/recently used absolute ranges/i)).not.toBeInTheDocument();
|
||||||
createTimeRange('2019-12-17T07:48:27.433Z', '2019-12-18T07:48:27.433Z'),
|
expect(
|
||||||
createTimeRange('2019-10-17T07:48:27.433Z', '2019-10-18T07:48:27.433Z'),
|
screen.queryByText(
|
||||||
];
|
/it looks like you haven't used this time picker before\. as soon as you enter some time intervals, recently used intervals will appear here\./i
|
||||||
|
)
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
const wrapper = shallow(
|
it('renders without history', () => {
|
||||||
<TimePickerContentWithScreenSize
|
renderComponent({ value: absoluteValue, isFullscreen: false, history, showHistory: false });
|
||||||
onChangeTimeZone={() => {}}
|
expect(screen.queryByText(/recently used absolute ranges/i)).not.toBeInTheDocument();
|
||||||
onChange={value => {}}
|
expect(screen.queryByText(/2019-12-17 07:48:27 to 2019-12-17 07:49:27/i)).not.toBeInTheDocument();
|
||||||
timeZone="utc"
|
expect(screen.queryByText(/2019-10-18 07:50:27 to 2019-10-18 07:51:27/i)).not.toBeInTheDocument();
|
||||||
value={value}
|
});
|
||||||
isFullscreen={true}
|
|
||||||
history={history}
|
it('renders with relative picker', () => {
|
||||||
/>
|
renderComponent({ value: absoluteValue, isFullscreen: false });
|
||||||
);
|
expect(screen.queryByText(/relative time ranges/i)).toBeInTheDocument();
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(screen.queryByText(/other quick ranges/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders without relative picker', () => {
|
||||||
|
renderComponent({ value: absoluteValue, isFullscreen: false, hideQuickRanges: true });
|
||||||
|
expect(screen.queryByText(/relative time ranges/i)).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/other quick ranges/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with absolute picker when absolute value and quick ranges are visible', () => {
|
||||||
|
renderComponent({ value: absoluteValue, isFullscreen: false });
|
||||||
|
expect(screen.queryByLabelText(/timepicker from field/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with absolute picker when absolute value and quick ranges are hidden', () => {
|
||||||
|
renderComponent({ value: absoluteValue, isFullscreen: false, hideQuickRanges: true });
|
||||||
|
expect(screen.queryByLabelText(/timepicker from field/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders without absolute picker when narrow screen and quick ranges are visible', () => {
|
||||||
|
renderComponent({ value: relativeValue, isFullscreen: false });
|
||||||
|
expect(screen.queryByLabelText(/timepicker from field/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with absolute picker when narrow screen and quick ranges are hidden', () => {
|
||||||
|
renderComponent({ value: relativeValue, isFullscreen: false, hideQuickRanges: true });
|
||||||
|
expect(screen.queryByLabelText(/timepicker from field/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders without timezone picker', () => {
|
||||||
|
renderComponent({ value: absoluteValue, hideTimeZone: true });
|
||||||
|
expect(screen.queryByText(/coordinated universal time/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createTimeRange(from: string, to: string): TimeRange {
|
function noop(): {} {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderComponent({
|
||||||
|
value,
|
||||||
|
isFullscreen = true,
|
||||||
|
showHistory = true,
|
||||||
|
history = [],
|
||||||
|
hideQuickRanges = false,
|
||||||
|
hideTimeZone = false,
|
||||||
|
}: Pick<PropsWithScreenSize, 'value'> & Partial<PropsWithScreenSize>): RenderResult {
|
||||||
|
return render(
|
||||||
|
<TimePickerContentWithScreenSize
|
||||||
|
onChangeTimeZone={noop}
|
||||||
|
onChange={noop}
|
||||||
|
timeZone="utc"
|
||||||
|
value={value}
|
||||||
|
isFullscreen={isFullscreen}
|
||||||
|
showHistory={showHistory}
|
||||||
|
history={history}
|
||||||
|
hideQuickRanges={hideQuickRanges}
|
||||||
|
hideTimeZone={hideTimeZone}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRelativeTimeRange(): TimeRange {
|
||||||
|
const now = dateTime();
|
||||||
|
const now5m = now.subtract(5, 'm');
|
||||||
|
|
||||||
|
return {
|
||||||
|
from: now5m,
|
||||||
|
to: now,
|
||||||
|
raw: { from: 'now-5m', to: 'now' },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAbsoluteTimeRange(from: string, to: string): TimeRange {
|
||||||
return {
|
return {
|
||||||
from: dateTime(from),
|
from: dateTime(from),
|
||||||
to: dateTime(to),
|
to: dateTime(to),
|
||||||
|
@ -11,7 +11,7 @@ import { TimeRangeForm } from './TimeRangeForm';
|
|||||||
import { TimeRangeList } from './TimeRangeList';
|
import { TimeRangeList } from './TimeRangeList';
|
||||||
import { TimePickerFooter } from './TimePickerFooter';
|
import { TimePickerFooter } from './TimePickerFooter';
|
||||||
|
|
||||||
const getStyles = stylesFactory((theme: GrafanaTheme, isReversed) => {
|
const getStyles = stylesFactory((theme: GrafanaTheme, isReversed, hideQuickRanges, isContainerTall) => {
|
||||||
const containerBorder = theme.isDark ? theme.palette.dark9 : theme.palette.gray5;
|
const containerBorder = theme.isDark ? theme.palette.dark9 : theme.palette.gray5;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -19,12 +19,12 @@ const getStyles = stylesFactory((theme: GrafanaTheme, isReversed) => {
|
|||||||
background: ${theme.colors.bodyBg};
|
background: ${theme.colors.bodyBg};
|
||||||
box-shadow: 0px 0px 20px ${theme.colors.dropdownShadow};
|
box-shadow: 0px 0px 20px ${theme.colors.dropdownShadow};
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: ${theme.zIndex.modal};
|
z-index: ${theme.zIndex.dropdown};
|
||||||
width: 546px;
|
width: 546px;
|
||||||
top: 116%;
|
top: 116%;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
border: 1px solid ${containerBorder};
|
border: 1px solid ${containerBorder};
|
||||||
right: ${isReversed ? 'unset' : 0};
|
${isReversed ? 'left' : 'right'}: 0;
|
||||||
|
|
||||||
@media only screen and (max-width: ${theme.breakpoints.lg}) {
|
@media only screen and (max-width: ${theme.breakpoints.lg}) {
|
||||||
width: 262px;
|
width: 262px;
|
||||||
@ -32,19 +32,15 @@ const getStyles = stylesFactory((theme: GrafanaTheme, isReversed) => {
|
|||||||
`,
|
`,
|
||||||
body: css`
|
body: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 381px;
|
height: ${isContainerTall ? '381px' : '217px'};
|
||||||
`,
|
`,
|
||||||
leftSide: css`
|
leftSide: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-right: ${isReversed ? 'none' : `1px solid ${theme.colors.border1}`};
|
border-right: ${isReversed ? 'none' : `1px solid ${theme.colors.border1}`};
|
||||||
width: 60%;
|
width: ${!hideQuickRanges ? '60%' : '100%'};
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
order: ${isReversed ? 1 : 0};
|
order: ${isReversed ? 1 : 0};
|
||||||
|
|
||||||
@media only screen and (max-width: ${theme.breakpoints.lg}) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
rightSide: css`
|
rightSide: css`
|
||||||
width: 40% !important;
|
width: 40% !important;
|
||||||
@ -61,8 +57,6 @@ const getStyles = stylesFactory((theme: GrafanaTheme, isReversed) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getNarrowScreenStyles = stylesFactory((theme: GrafanaTheme) => {
|
const getNarrowScreenStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
const formBackground = theme.isDark ? theme.palette.gray15 : theme.palette.gray98;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
header: css`
|
header: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -74,7 +68,6 @@ const getNarrowScreenStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
`,
|
`,
|
||||||
body: css`
|
body: css`
|
||||||
border-bottom: 1px solid ${theme.colors.border1};
|
border-bottom: 1px solid ${theme.colors.border1};
|
||||||
background: ${formBackground};
|
|
||||||
box-shadow: inset 0px 2px 2px ${theme.colors.dropdownShadow};
|
box-shadow: inset 0px 2px 2px ${theme.colors.dropdownShadow};
|
||||||
`,
|
`,
|
||||||
form: css`
|
form: css`
|
||||||
@ -83,12 +76,12 @@ const getNarrowScreenStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const getFullScreenStyles = stylesFactory((theme: GrafanaTheme) => {
|
const getFullScreenStyles = stylesFactory((theme: GrafanaTheme, hideQuickRanges?: boolean) => {
|
||||||
return {
|
return {
|
||||||
container: css`
|
container: css`
|
||||||
padding-top: 9px;
|
padding-top: 9px;
|
||||||
padding-left: 11px;
|
padding-left: 11px;
|
||||||
padding-right: 20%;
|
padding-right: ${!hideQuickRanges ? '20%' : '11px'};
|
||||||
`,
|
`,
|
||||||
title: css`
|
title: css`
|
||||||
margin-bottom: 11px;
|
margin-bottom: 11px;
|
||||||
@ -135,51 +128,74 @@ interface Props {
|
|||||||
hideTimeZone?: boolean;
|
hideTimeZone?: boolean;
|
||||||
/** Reverse the order of relative and absolute range pickers. Used to left align the picker in forms */
|
/** Reverse the order of relative and absolute range pickers. Used to left align the picker in forms */
|
||||||
isReversed?: boolean;
|
isReversed?: boolean;
|
||||||
|
hideQuickRanges?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PropsWithScreenSize extends Props {
|
export interface PropsWithScreenSize extends Props {
|
||||||
isFullscreen: boolean;
|
isFullscreen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormProps extends Omit<Props, 'history'> {
|
interface FormProps extends Omit<Props, 'history'> {
|
||||||
visible: boolean;
|
|
||||||
historyOptions?: TimeOption[];
|
historyOptions?: TimeOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TimePickerContentWithScreenSize: React.FC<PropsWithScreenSize> = props => {
|
export const TimePickerContentWithScreenSize: React.FC<PropsWithScreenSize> = props => {
|
||||||
|
const {
|
||||||
|
quickOptions = [],
|
||||||
|
otherOptions = [],
|
||||||
|
isReversed,
|
||||||
|
isFullscreen,
|
||||||
|
hideQuickRanges,
|
||||||
|
timeZone,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
history,
|
||||||
|
showHistory,
|
||||||
|
className,
|
||||||
|
hideTimeZone,
|
||||||
|
onChangeTimeZone,
|
||||||
|
} = props;
|
||||||
|
const isHistoryEmpty = !history?.length;
|
||||||
|
const isContainerTall =
|
||||||
|
(isFullscreen && showHistory) || (!isFullscreen && ((showHistory && !isHistoryEmpty) || !hideQuickRanges));
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const styles = getStyles(theme, props.isReversed);
|
const styles = getStyles(theme, isReversed, hideQuickRanges, isContainerTall);
|
||||||
const historyOptions = mapToHistoryOptions(props.history, props.timeZone);
|
const historyOptions = mapToHistoryOptions(history, timeZone);
|
||||||
const { quickOptions = [], otherOptions = [], isFullscreen } = props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(styles.container, props.className)}>
|
<div className={cx(styles.container, className)}>
|
||||||
<div className={styles.body}>
|
<div className={styles.body}>
|
||||||
<div className={styles.leftSide}>
|
{isFullscreen && (
|
||||||
<FullScreenForm {...props} visible={isFullscreen} historyOptions={historyOptions} />
|
<div className={styles.leftSide}>
|
||||||
</div>
|
<FullScreenForm {...props} historyOptions={historyOptions} />
|
||||||
<CustomScrollbar className={styles.rightSide}>
|
</div>
|
||||||
<NarrowScreenForm {...props} visible={!isFullscreen} historyOptions={historyOptions} />
|
)}
|
||||||
<TimeRangeList
|
{(!isFullscreen || !hideQuickRanges) && (
|
||||||
title="Relative time ranges"
|
<CustomScrollbar className={styles.rightSide}>
|
||||||
options={quickOptions}
|
{!isFullscreen && <NarrowScreenForm {...props} historyOptions={historyOptions} />}
|
||||||
onSelect={props.onChange}
|
{!hideQuickRanges && (
|
||||||
value={props.value}
|
<>
|
||||||
timeZone={props.timeZone}
|
<TimeRangeList
|
||||||
/>
|
title="Relative time ranges"
|
||||||
<div className={styles.spacing} />
|
options={quickOptions}
|
||||||
<TimeRangeList
|
onSelect={onChange}
|
||||||
title="Other quick ranges"
|
value={value}
|
||||||
options={otherOptions}
|
timeZone={timeZone}
|
||||||
onSelect={props.onChange}
|
/>
|
||||||
value={props.value}
|
<div className={styles.spacing} />
|
||||||
timeZone={props.timeZone}
|
<TimeRangeList
|
||||||
/>
|
title="Other quick ranges"
|
||||||
</CustomScrollbar>
|
options={otherOptions}
|
||||||
|
onSelect={onChange}
|
||||||
|
value={value}
|
||||||
|
timeZone={timeZone}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</CustomScrollbar>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!props.hideTimeZone && isFullscreen && (
|
{!hideTimeZone && isFullscreen && <TimePickerFooter timeZone={timeZone} onChangeTimeZone={onChangeTimeZone} />}
|
||||||
<TimePickerFooter timeZone={props.timeZone} onChangeTimeZone={props.onChangeTimeZone} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -192,43 +208,40 @@ export const TimePickerContent: React.FC<Props> = props => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const NarrowScreenForm: React.FC<FormProps> = props => {
|
const NarrowScreenForm: React.FC<FormProps> = props => {
|
||||||
|
const { value, hideQuickRanges, onChange, timeZone, historyOptions = [], showHistory } = props;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const styles = getNarrowScreenStyles(theme);
|
const styles = getNarrowScreenStyles(theme);
|
||||||
const isAbsolute = isDateTime(props.value.raw.from) || isDateTime(props.value.raw.to);
|
const isAbsolute = isDateTime(value.raw.from) || isDateTime(value.raw.to);
|
||||||
const [collapsed, setCollapsed] = useState(isAbsolute);
|
const [collapsedFlag, setCollapsedFlag] = useState(!isAbsolute);
|
||||||
|
const collapsed = hideQuickRanges ? false : collapsedFlag;
|
||||||
if (!props.visible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
aria-label="TimePicker absolute time range"
|
aria-label="TimePicker absolute time range"
|
||||||
className={styles.header}
|
className={styles.header}
|
||||||
onClick={() => setCollapsed(!collapsed)}
|
onClick={() => {
|
||||||
|
if (!hideQuickRanges) {
|
||||||
|
setCollapsedFlag(!collapsed);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<TimePickerTitle>Absolute time range</TimePickerTitle>
|
<TimePickerTitle>Absolute time range</TimePickerTitle>
|
||||||
{<Icon name={collapsed ? 'angle-up' : 'angle-down'} />}
|
{!hideQuickRanges && <Icon name={!collapsed ? 'angle-up' : 'angle-down'} />}
|
||||||
</div>
|
</div>
|
||||||
{collapsed && (
|
{!collapsed && (
|
||||||
<div className={styles.body}>
|
<div className={styles.body}>
|
||||||
<div className={styles.form}>
|
<div className={styles.form}>
|
||||||
<TimeRangeForm
|
<TimeRangeForm value={value} onApply={onChange} timeZone={timeZone} isFullscreen={false} />
|
||||||
value={props.value}
|
|
||||||
onApply={props.onChange}
|
|
||||||
timeZone={props.timeZone}
|
|
||||||
isFullscreen={false}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{props.showHistory && (
|
{showHistory && (
|
||||||
<TimeRangeList
|
<TimeRangeList
|
||||||
title="Recently used absolute ranges"
|
title="Recently used absolute ranges"
|
||||||
options={props.historyOptions || []}
|
options={historyOptions}
|
||||||
onSelect={props.onChange}
|
onSelect={onChange}
|
||||||
value={props.value}
|
value={value}
|
||||||
placeholderEmpty={null}
|
placeholderEmpty={null}
|
||||||
timeZone={props.timeZone}
|
timeZone={timeZone}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -239,11 +252,7 @@ const NarrowScreenForm: React.FC<FormProps> = props => {
|
|||||||
|
|
||||||
const FullScreenForm: React.FC<FormProps> = props => {
|
const FullScreenForm: React.FC<FormProps> = props => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const styles = getFullScreenStyles(theme);
|
const styles = getFullScreenStyles(theme, props.hideQuickRanges);
|
||||||
|
|
||||||
if (!props.visible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -32,7 +32,7 @@ interface InputState {
|
|||||||
const errorMessage = 'Please enter a past date or "now"';
|
const errorMessage = 'Please enter a past date or "now"';
|
||||||
|
|
||||||
export const TimeRangeForm: React.FC<Props> = props => {
|
export const TimeRangeForm: React.FC<Props> = props => {
|
||||||
const { value, isFullscreen = false, timeZone, onApply: onApplyFromProps } = props;
|
const { value, isFullscreen = false, timeZone, onApply: onApplyFromProps, isReversed } = props;
|
||||||
|
|
||||||
const [from, setFrom] = useState<InputState>(valueToState(value.raw.from, false, timeZone));
|
const [from, setFrom] = useState<InputState>(valueToState(value.raw.from, false, timeZone));
|
||||||
const [to, setTo] = useState<InputState>(valueToState(value.raw.to, true, timeZone));
|
const [to, setTo] = useState<InputState>(valueToState(value.raw.to, true, timeZone));
|
||||||
@ -122,7 +122,7 @@ export const TimeRangeForm: React.FC<Props> = props => {
|
|||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
timeZone={timeZone}
|
timeZone={timeZone}
|
||||||
isReversed={props.isReversed}
|
isReversed={isReversed}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,370 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`TimePickerContent renders correctly in full screen 1`] = `
|
|
||||||
<div
|
|
||||||
className="css-ajr8sn"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="css-ooqtr4"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="css-1f2wc71"
|
|
||||||
>
|
|
||||||
<FullScreenForm
|
|
||||||
historyOptions={Array []}
|
|
||||||
isFullscreen={true}
|
|
||||||
onChange={[Function]}
|
|
||||||
onChangeTimeZone={[Function]}
|
|
||||||
timeZone="utc"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visible={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<CustomScrollbar
|
|
||||||
autoHeightMax="100%"
|
|
||||||
autoHeightMin="0"
|
|
||||||
autoHide={false}
|
|
||||||
autoHideDuration={200}
|
|
||||||
autoHideTimeout={200}
|
|
||||||
className="css-10t714z"
|
|
||||||
hideTracksWhenNotNeeded={false}
|
|
||||||
setScrollTop={[Function]}
|
|
||||||
>
|
|
||||||
<NarrowScreenForm
|
|
||||||
historyOptions={Array []}
|
|
||||||
isFullscreen={true}
|
|
||||||
onChange={[Function]}
|
|
||||||
onChangeTimeZone={[Function]}
|
|
||||||
timeZone="utc"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visible={false}
|
|
||||||
/>
|
|
||||||
<TimeRangeList
|
|
||||||
onSelect={[Function]}
|
|
||||||
options={Array []}
|
|
||||||
timeZone="utc"
|
|
||||||
title="Relative time ranges"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="css-1ogeuxc"
|
|
||||||
/>
|
|
||||||
<TimeRangeList
|
|
||||||
onSelect={[Function]}
|
|
||||||
options={Array []}
|
|
||||||
timeZone="utc"
|
|
||||||
title="Other quick ranges"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</CustomScrollbar>
|
|
||||||
</div>
|
|
||||||
<TimePickerFooter
|
|
||||||
onChangeTimeZone={[Function]}
|
|
||||||
timeZone="utc"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`TimePickerContent renders correctly in narrow screen 1`] = `
|
|
||||||
<div
|
|
||||||
className="css-ajr8sn"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="css-ooqtr4"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="css-1f2wc71"
|
|
||||||
>
|
|
||||||
<FullScreenForm
|
|
||||||
historyOptions={Array []}
|
|
||||||
isFullscreen={false}
|
|
||||||
onChange={[Function]}
|
|
||||||
onChangeTimeZone={[Function]}
|
|
||||||
timeZone="utc"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visible={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<CustomScrollbar
|
|
||||||
autoHeightMax="100%"
|
|
||||||
autoHeightMin="0"
|
|
||||||
autoHide={false}
|
|
||||||
autoHideDuration={200}
|
|
||||||
autoHideTimeout={200}
|
|
||||||
className="css-10t714z"
|
|
||||||
hideTracksWhenNotNeeded={false}
|
|
||||||
setScrollTop={[Function]}
|
|
||||||
>
|
|
||||||
<NarrowScreenForm
|
|
||||||
historyOptions={Array []}
|
|
||||||
isFullscreen={false}
|
|
||||||
onChange={[Function]}
|
|
||||||
onChangeTimeZone={[Function]}
|
|
||||||
timeZone="utc"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visible={true}
|
|
||||||
/>
|
|
||||||
<TimeRangeList
|
|
||||||
onSelect={[Function]}
|
|
||||||
options={Array []}
|
|
||||||
timeZone="utc"
|
|
||||||
title="Relative time ranges"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="css-1ogeuxc"
|
|
||||||
/>
|
|
||||||
<TimeRangeList
|
|
||||||
onSelect={[Function]}
|
|
||||||
options={Array []}
|
|
||||||
timeZone="utc"
|
|
||||||
title="Other quick ranges"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</CustomScrollbar>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`TimePickerContent renders recent absolute ranges correctly 1`] = `
|
|
||||||
<div
|
|
||||||
className="css-ajr8sn"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="css-ooqtr4"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="css-1f2wc71"
|
|
||||||
>
|
|
||||||
<FullScreenForm
|
|
||||||
history={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"from": "2019-10-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-10-17T07:48:27.433Z",
|
|
||||||
"to": "2019-10-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-10-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
historyOptions={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"display": "2019-12-17 07:48:27 to 2019-12-18 07:48:27",
|
|
||||||
"from": "2019-12-17 07:48:27",
|
|
||||||
"section": 3,
|
|
||||||
"to": "2019-12-18 07:48:27",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"display": "2019-10-17 07:48:27 to 2019-10-18 07:48:27",
|
|
||||||
"from": "2019-10-17 07:48:27",
|
|
||||||
"section": 3,
|
|
||||||
"to": "2019-10-18 07:48:27",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
isFullscreen={true}
|
|
||||||
onChange={[Function]}
|
|
||||||
onChangeTimeZone={[Function]}
|
|
||||||
timeZone="utc"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visible={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<CustomScrollbar
|
|
||||||
autoHeightMax="100%"
|
|
||||||
autoHeightMin="0"
|
|
||||||
autoHide={false}
|
|
||||||
autoHideDuration={200}
|
|
||||||
autoHideTimeout={200}
|
|
||||||
className="css-10t714z"
|
|
||||||
hideTracksWhenNotNeeded={false}
|
|
||||||
setScrollTop={[Function]}
|
|
||||||
>
|
|
||||||
<NarrowScreenForm
|
|
||||||
history={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"from": "2019-10-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-10-17T07:48:27.433Z",
|
|
||||||
"to": "2019-10-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-10-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
historyOptions={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"display": "2019-12-17 07:48:27 to 2019-12-18 07:48:27",
|
|
||||||
"from": "2019-12-17 07:48:27",
|
|
||||||
"section": 3,
|
|
||||||
"to": "2019-12-18 07:48:27",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"display": "2019-10-17 07:48:27 to 2019-10-18 07:48:27",
|
|
||||||
"from": "2019-10-17 07:48:27",
|
|
||||||
"section": 3,
|
|
||||||
"to": "2019-10-18 07:48:27",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
isFullscreen={true}
|
|
||||||
onChange={[Function]}
|
|
||||||
onChangeTimeZone={[Function]}
|
|
||||||
timeZone="utc"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visible={false}
|
|
||||||
/>
|
|
||||||
<TimeRangeList
|
|
||||||
onSelect={[Function]}
|
|
||||||
options={Array []}
|
|
||||||
timeZone="utc"
|
|
||||||
title="Relative time ranges"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="css-1ogeuxc"
|
|
||||||
/>
|
|
||||||
<TimeRangeList
|
|
||||||
onSelect={[Function]}
|
|
||||||
options={Array []}
|
|
||||||
timeZone="utc"
|
|
||||||
title="Other quick ranges"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</CustomScrollbar>
|
|
||||||
</div>
|
|
||||||
<TimePickerFooter
|
|
||||||
onChangeTimeZone={[Function]}
|
|
||||||
timeZone="utc"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
@ -1,11 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { LocalStorageValueProvider } from '../LocalStorageValueProvider';
|
import { LocalStorageValueProvider } from '../LocalStorageValueProvider';
|
||||||
import { TimeRange, isDateTime, toUtc } from '@grafana/data';
|
import { TimeRange, isDateTime, toUtc } from '@grafana/data';
|
||||||
import { Props as TimePickerProps, TimeRangePicker } from '@grafana/ui/src/components/TimePicker/TimeRangePicker';
|
import { TimeRangePickerProps, TimeRangePicker } from '@grafana/ui/src/components/TimePicker/TimeRangePicker';
|
||||||
|
|
||||||
const LOCAL_STORAGE_KEY = 'grafana.dashboard.timepicker.history';
|
const LOCAL_STORAGE_KEY = 'grafana.dashboard.timepicker.history';
|
||||||
|
|
||||||
interface Props extends Omit<TimePickerProps, 'history' | 'theme'> {}
|
interface Props extends Omit<TimeRangePickerProps, 'history' | 'theme'> {}
|
||||||
|
|
||||||
export const TimePickerWithHistory: React.FC<Props> = props => {
|
export const TimePickerWithHistory: React.FC<Props> = props => {
|
||||||
return (
|
return (
|
||||||
|
23
yarn.lock
23
yarn.lock
@ -5731,6 +5731,20 @@
|
|||||||
lodash "^4.17.15"
|
lodash "^4.17.15"
|
||||||
redent "^3.0.0"
|
redent "^3.0.0"
|
||||||
|
|
||||||
|
"@testing-library/jest-dom@5.11.9":
|
||||||
|
version "5.11.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.11.9.tgz#e6b3cd687021f89f261bd53cbe367041fbd3e975"
|
||||||
|
integrity sha512-Mn2gnA9d1wStlAIT2NU8J15LNob0YFBVjs2aEQ3j8rsfRQo+lAs7/ui1i2TGaJjapLmuNPLTsrm+nPjmZDwpcQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.9.2"
|
||||||
|
"@types/testing-library__jest-dom" "^5.9.1"
|
||||||
|
aria-query "^4.2.2"
|
||||||
|
chalk "^3.0.0"
|
||||||
|
css "^3.0.0"
|
||||||
|
css.escape "^1.5.1"
|
||||||
|
lodash "^4.17.15"
|
||||||
|
redent "^3.0.0"
|
||||||
|
|
||||||
"@testing-library/react-hooks@^3.2.1":
|
"@testing-library/react-hooks@^3.2.1":
|
||||||
version "3.2.1"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-3.2.1.tgz#19b6caa048ef15faa69d439c469033873ea01294"
|
resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-3.2.1.tgz#19b6caa048ef15faa69d439c469033873ea01294"
|
||||||
@ -22933,15 +22947,6 @@ rollup@^0.25.8:
|
|||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
source-map-support "^0.3.2"
|
source-map-support "^0.3.2"
|
||||||
|
|
||||||
rollup@^0.25.8:
|
|
||||||
version "0.25.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.25.8.tgz#bf6ce83b87510d163446eeaa577ed6a6fc5835e0"
|
|
||||||
integrity sha1-v2zoO4dRDRY0Ru6qV37WpvxYNeA=
|
|
||||||
dependencies:
|
|
||||||
chalk "^1.1.1"
|
|
||||||
minimist "^1.2.0"
|
|
||||||
source-map-support "^0.3.2"
|
|
||||||
|
|
||||||
rollup@^0.63.4:
|
rollup@^0.63.4:
|
||||||
version "0.63.5"
|
version "0.63.5"
|
||||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.63.5.tgz#5543eecac9a1b83b7e1be598b5be84c9c0a089db"
|
resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.63.5.tgz#5543eecac9a1b83b7e1be598b5be84c9c0a089db"
|
||||||
|
Loading…
Reference in New Issue
Block a user