Commit 46167785 by Bogdan Matei Committed by GitHub

Grafana-UI: Enhances for TimeRangePicker and TimeRangeInput (#30102)

parent 9734b706
......@@ -39,6 +39,7 @@
"@types/react-color": "3.0.1",
"@types/react-select": "3.0.8",
"@types/react-table": "7.0.12",
"@testing-library/jest-dom": "5.11.9",
"@sentry/browser": "5.25.0",
"@types/slate": "0.47.1",
"@types/slate-react": "0.22.5",
......
import { Story } from '@storybook/react';
import React from 'react';
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 { UseState } from '../../utils/storybook/UseState';
import { TimeRangeInput } from '@grafana/ui';
import { TimeRangeInputProps } from './TimeRangeInput';
import mdx from './TimeRangeInput.mdx';
export default {
......@@ -17,51 +19,75 @@ export default {
},
};
export const basic = () => {
return (
<UseState
initialState={{
from: dateTime(),
to: dateTime(),
raw: { from: 'now-6h' as TimeFragment, to: 'now' as TimeFragment },
}}
>
{(value, updateValue) => {
return (
<TimeRangeInput
value={value}
onChange={timeRange => {
action('onChange fired')(timeRange);
updateValue(timeRange);
}}
/>
);
}}
</UseState>
interface State {
value: TimeRange;
timeZone: TimeZone;
}
const getComponentWithState = (initialState: State, props: TimeRangeInputProps) => (
<UseState initialState={initialState}>
{(state, updateValue) => {
return (
<TimeRangeInput
{...props}
value={state.value}
timeZone={state.timeZone}
onChange={value => {
action('onChange fired')(value);
updateValue({
...state,
value,
});
}}
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 = () => {
return (
<UseState
initialState={{
from: dateTime(),
to: dateTime(),
raw: { from: 'now-6h' as TimeFragment, to: 'now' as TimeFragment },
}}
>
{(value, updateValue) => {
return (
<TimeRangeInput
clearable
value={value}
onChange={timeRange => {
action('onChange fired')(timeRange);
updateValue(timeRange);
}}
/>
);
}}
</UseState>
export const Absolute: Story<TimeRangeInputProps> = props => {
const to = dateTime();
const from = to.subtract(6, 'h');
return getComponentWithState(
{
value: {
from,
to,
raw: {
from,
to,
},
},
timeZone: DefaultTimeZone,
},
props
);
};
......@@ -14,7 +14,7 @@ const isValidTimeRange = (range: any) => {
return dateMath.isValid(range.from) && dateMath.isValid(range.to);
};
export interface Props {
export interface TimeRangeInputProps {
value: TimeRange;
timeZone?: TimeZone;
onChange: (timeRange: TimeRange) => void;
......@@ -22,18 +22,22 @@ export interface Props {
hideTimeZone?: boolean;
placeholder?: string;
clearable?: boolean;
isReversed?: boolean;
hideQuickRanges?: boolean;
}
const noop = () => {};
export const TimeRangeInput: FC<Props> = ({
export const TimeRangeInput: FC<TimeRangeInputProps> = ({
value,
onChange,
onChangeTimeZone,
onChangeTimeZone = noop,
clearable,
hideTimeZone = true,
timeZone = 'browser',
placeholder = 'Select time range',
isReversed = true,
hideQuickRanges = false,
}) => {
const [isOpen, setIsOpen] = useState(false);
const styles = useStyles(getStyles);
......@@ -84,10 +88,11 @@ export const TimeRangeInput: FC<Props> = ({
onChange={onRangeChange}
otherOptions={otherOptions}
quickOptions={quickOptions}
onChangeTimeZone={onChangeTimeZone || noop}
onChangeTimeZone={onChangeTimeZone}
className={styles.content}
hideTimeZone={hideTimeZone}
isReversed
isReversed={isReversed}
hideQuickRanges={hideQuickRanges}
/>
</ClickOutsideWrapper>
)}
......
import { Story } from '@storybook/react';
import React from 'react';
import { action } from '@storybook/addon-actions';
import { TimeRangePicker } from '@grafana/ui';
import { Button, TimeRangePicker } from '@grafana/ui';
import { UseState } from '../../utils/storybook/UseState';
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 {
title: 'Pickers and Editors/TimePickers/TimeRangePicker',
......@@ -12,24 +14,37 @@ export default {
decorators: [withCenteredStory],
};
export const basic = () => {
return (
<UseState
initialState={{
from: dateTime(),
to: dateTime(),
raw: { from: 'now-6h' as TimeFragment, to: 'now' as TimeFragment },
}}
>
{(value, updateValue) => {
return (
interface State {
value: TimeRange;
timeZone: TimeZone;
history: TimeRange[];
}
const getComponentWithState = (initialState: State, props: TimeRangePickerProps) => (
<UseState initialState={initialState}>
{(state, updateValue) => {
return (
<>
<TimeRangePicker
onChangeTimeZone={() => {}}
timeZone="browser"
value={value}
onChange={timeRange => {
action('onChange fired')(timeRange);
updateValue(timeRange);
{...props}
timeZone={state.timeZone}
value={state.value}
history={state.history}
onChange={value => {
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={() => {
action('onMoveBackward fired')();
......@@ -41,8 +56,60 @@ export const basic = () => {
action('onZoom fired')();
}}
/>
);
}}
</UseState>
<Button
onClick={() => {
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
);
};
import React from 'react';
import { mount } from 'enzyme';
import { UnthemedTimeRangePicker } from './TimeRangePicker';
import { dateTime, TimeRange } from '@grafana/data';
import { render } from '@testing-library/react';
import React from 'react';
import dark from '../../themes/dark';
import { UnthemedTimeRangePicker } from './TimeRangePicker';
const from = '2019-12-17T07:48:27.433Z';
const to = '2019-12-18T07:48:27.433Z';
const from = dateTime('2019-12-17T07:48:27.433Z');
const to = dateTime('2019-12-18T07:48:27.433Z');
const value: TimeRange = {
from: dateTime(from),
to: dateTime(to),
raw: { from: dateTime(from), to: dateTime(to) },
from,
to,
raw: { from, to },
};
describe('TimePicker', () => {
it('renders buttons correctly', () => {
const wrapper = mount(
const container = render(
<UnthemedTimeRangePicker
onChangeTimeZone={() => {}}
onChange={value => {}}
......@@ -26,6 +26,7 @@ describe('TimePicker', () => {
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;
value: TimeRange;
timeZone?: TimeZone;
......@@ -68,13 +68,14 @@ export interface Props extends Themeable {
onMoveForward: () => void;
onZoom: () => void;
history?: TimeRange[];
hideQuickRanges?: boolean;
}
export interface State {
isOpen: boolean;
}
export class UnthemedTimeRangePicker extends PureComponent<Props, State> {
export class UnthemedTimeRangePicker extends PureComponent<TimeRangePickerProps, State> {
state: State = {
isOpen: false,
};
......@@ -107,6 +108,7 @@ export class UnthemedTimeRangePicker extends PureComponent<Props, State> {
theme,
history,
onChangeTimeZone,
hideQuickRanges,
} = this.props;
const { isOpen } = this.state;
......@@ -146,6 +148,7 @@ export class UnthemedTimeRangePicker extends PureComponent<Props, State> {
history={history}
showHistory
onChangeTimeZone={onChangeTimeZone}
hideQuickRanges={hideQuickRanges}
/>
</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 }) => {
const theme = useTheme();
......
import React from 'react';
import { shallow } from 'enzyme';
import { TimePickerContentWithScreenSize } from './TimePickerContent';
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', () => {
it('renders correctly in full screen', () => {
const value = createTimeRange('2019-12-17T07:48:27.433Z', '2019-12-18T07:48:27.433Z');
const wrapper = shallow(
<TimePickerContentWithScreenSize
onChangeTimeZone={() => {}}
onChange={value => {}}
timeZone="utc"
value={value}
isFullscreen={true}
/>
);
expect(wrapper).toMatchSnapshot();
});
const absoluteValue = createAbsoluteTimeRange('2019-12-17T07:48:27.433Z', '2019-12-18T07:49:27.433Z');
const relativeValue = createRelativeTimeRange();
const history = [
createAbsoluteTimeRange('2019-12-17T07:48:27.433Z', '2019-12-17T07:49:27.433Z'),
createAbsoluteTimeRange('2019-10-18T07:50:27.433Z', '2019-10-18T07:51:27.433Z'),
];
describe('Wide Screen', () => {
it('renders with history', () => {
renderComponent({ value: absoluteValue, history });
expect(screen.queryByText(/recently used absolute ranges/i)).toBeInTheDocument();
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 correctly in narrow screen', () => {
const value = createTimeRange('2019-12-17T07:48:27.433Z', '2019-12-18T07:48:27.433Z');
const wrapper = shallow(
<TimePickerContentWithScreenSize
onChangeTimeZone={() => {}}
onChange={value => {}}
timeZone="utc"
value={value}
isFullscreen={false}
/>
);
expect(wrapper).toMatchSnapshot();
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 recent absolute ranges correctly', () => {
const value = createTimeRange('2019-12-17T07:48:27.433Z', '2019-12-18T07:48:27.433Z');
const history = [
createTimeRange('2019-12-17T07:48:27.433Z', '2019-12-18T07:48:27.433Z'),
createTimeRange('2019-10-17T07:48:27.433Z', '2019-10-18T07:48:27.433Z'),
];
const wrapper = shallow(
<TimePickerContentWithScreenSize
onChangeTimeZone={() => {}}
onChange={value => {}}
timeZone="utc"
value={value}
isFullscreen={true}
history={history}
/>
);
expect(wrapper).toMatchSnapshot();
describe('Narrow Screen', () => {
it('renders with history', () => {
renderComponent({ value: absoluteValue, history, isFullscreen: false });
expect(screen.queryByText(/recently used absolute ranges/i)).toBeInTheDocument();
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, isFullscreen: false });
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
)
).not.toBeInTheDocument();
});
it('renders without history', () => {
renderComponent({ value: absoluteValue, isFullscreen: false, 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, isFullscreen: false });
expect(screen.queryByText(/relative time ranges/i)).toBeInTheDocument();
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 {
from: dateTime(from),
to: dateTime(to),
......
......@@ -11,7 +11,7 @@ import { TimeRangeForm } from './TimeRangeForm';
import { TimeRangeList } from './TimeRangeList';
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;
return {
......@@ -19,12 +19,12 @@ const getStyles = stylesFactory((theme: GrafanaTheme, isReversed) => {
background: ${theme.colors.bodyBg};
box-shadow: 0px 0px 20px ${theme.colors.dropdownShadow};
position: absolute;
z-index: ${theme.zIndex.modal};
z-index: ${theme.zIndex.dropdown};
width: 546px;
top: 116%;
border-radius: 2px;
border: 1px solid ${containerBorder};
right: ${isReversed ? 'unset' : 0};
${isReversed ? 'left' : 'right'}: 0;
@media only screen and (max-width: ${theme.breakpoints.lg}) {
width: 262px;
......@@ -32,19 +32,15 @@ const getStyles = stylesFactory((theme: GrafanaTheme, isReversed) => {
`,
body: css`
display: flex;
height: 381px;
height: ${isContainerTall ? '381px' : '217px'};
`,
leftSide: css`
display: flex;
flex-direction: column;
border-right: ${isReversed ? 'none' : `1px solid ${theme.colors.border1}`};
width: 60%;
width: ${!hideQuickRanges ? '60%' : '100%'};
overflow: hidden;
order: ${isReversed ? 1 : 0};
@media only screen and (max-width: ${theme.breakpoints.lg}) {
display: none;
}
`,
rightSide: css`
width: 40% !important;
......@@ -61,8 +57,6 @@ const getStyles = stylesFactory((theme: GrafanaTheme, isReversed) => {
});
const getNarrowScreenStyles = stylesFactory((theme: GrafanaTheme) => {
const formBackground = theme.isDark ? theme.palette.gray15 : theme.palette.gray98;
return {
header: css`
display: flex;
......@@ -74,7 +68,6 @@ const getNarrowScreenStyles = stylesFactory((theme: GrafanaTheme) => {
`,
body: css`
border-bottom: 1px solid ${theme.colors.border1};
background: ${formBackground};
box-shadow: inset 0px 2px 2px ${theme.colors.dropdownShadow};
`,
form: css`
......@@ -83,12 +76,12 @@ const getNarrowScreenStyles = stylesFactory((theme: GrafanaTheme) => {
};
});
const getFullScreenStyles = stylesFactory((theme: GrafanaTheme) => {
const getFullScreenStyles = stylesFactory((theme: GrafanaTheme, hideQuickRanges?: boolean) => {
return {
container: css`
padding-top: 9px;
padding-left: 11px;
padding-right: 20%;
padding-right: ${!hideQuickRanges ? '20%' : '11px'};
`,
title: css`
margin-bottom: 11px;
......@@ -135,51 +128,74 @@ interface Props {
hideTimeZone?: boolean;
/** Reverse the order of relative and absolute range pickers. Used to left align the picker in forms */
isReversed?: boolean;
hideQuickRanges?: boolean;
}
interface PropsWithScreenSize extends Props {
export interface PropsWithScreenSize extends Props {
isFullscreen: boolean;
}
interface FormProps extends Omit<Props, 'history'> {
visible: boolean;
historyOptions?: TimeOption[];
}
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 styles = getStyles(theme, props.isReversed);
const historyOptions = mapToHistoryOptions(props.history, props.timeZone);
const { quickOptions = [], otherOptions = [], isFullscreen } = props;
const styles = getStyles(theme, isReversed, hideQuickRanges, isContainerTall);
const historyOptions = mapToHistoryOptions(history, timeZone);
return (
<div className={cx(styles.container, props.className)}>
<div className={cx(styles.container, className)}>
<div className={styles.body}>
<div className={styles.leftSide}>
<FullScreenForm {...props} visible={isFullscreen} historyOptions={historyOptions} />
</div>
<CustomScrollbar className={styles.rightSide}>
<NarrowScreenForm {...props} visible={!isFullscreen} historyOptions={historyOptions} />
<TimeRangeList
title="Relative time ranges"
options={quickOptions}
onSelect={props.onChange}
value={props.value}
timeZone={props.timeZone}
/>
<div className={styles.spacing} />
<TimeRangeList
title="Other quick ranges"
options={otherOptions}
onSelect={props.onChange}
value={props.value}
timeZone={props.timeZone}
/>
</CustomScrollbar>
{isFullscreen && (
<div className={styles.leftSide}>
<FullScreenForm {...props} historyOptions={historyOptions} />
</div>
)}
{(!isFullscreen || !hideQuickRanges) && (
<CustomScrollbar className={styles.rightSide}>
{!isFullscreen && <NarrowScreenForm {...props} historyOptions={historyOptions} />}
{!hideQuickRanges && (
<>
<TimeRangeList
title="Relative time ranges"
options={quickOptions}
onSelect={onChange}
value={value}
timeZone={timeZone}
/>
<div className={styles.spacing} />
<TimeRangeList
title="Other quick ranges"
options={otherOptions}
onSelect={onChange}
value={value}
timeZone={timeZone}
/>
</>
)}
</CustomScrollbar>
)}
</div>
{!props.hideTimeZone && isFullscreen && (
<TimePickerFooter timeZone={props.timeZone} onChangeTimeZone={props.onChangeTimeZone} />
)}
{!hideTimeZone && isFullscreen && <TimePickerFooter timeZone={timeZone} onChangeTimeZone={onChangeTimeZone} />}
</div>
);
};
......@@ -192,43 +208,40 @@ export const TimePickerContent: React.FC<Props> = props => {
};
const NarrowScreenForm: React.FC<FormProps> = props => {
const { value, hideQuickRanges, onChange, timeZone, historyOptions = [], showHistory } = props;
const theme = useTheme();
const styles = getNarrowScreenStyles(theme);
const isAbsolute = isDateTime(props.value.raw.from) || isDateTime(props.value.raw.to);
const [collapsed, setCollapsed] = useState(isAbsolute);
if (!props.visible) {
return null;
}
const isAbsolute = isDateTime(value.raw.from) || isDateTime(value.raw.to);
const [collapsedFlag, setCollapsedFlag] = useState(!isAbsolute);
const collapsed = hideQuickRanges ? false : collapsedFlag;
return (
<>
<div
aria-label="TimePicker absolute time range"
className={styles.header}
onClick={() => setCollapsed(!collapsed)}
onClick={() => {
if (!hideQuickRanges) {
setCollapsedFlag(!collapsed);
}
}}
>
<TimePickerTitle>Absolute time range</TimePickerTitle>
{<Icon name={collapsed ? 'angle-up' : 'angle-down'} />}
{!hideQuickRanges && <Icon name={!collapsed ? 'angle-up' : 'angle-down'} />}
</div>
{collapsed && (
{!collapsed && (
<div className={styles.body}>
<div className={styles.form}>
<TimeRangeForm
value={props.value}
onApply={props.onChange}
timeZone={props.timeZone}
isFullscreen={false}
/>
<TimeRangeForm value={value} onApply={onChange} timeZone={timeZone} isFullscreen={false} />
</div>
{props.showHistory && (
{showHistory && (
<TimeRangeList
title="Recently used absolute ranges"
options={props.historyOptions || []}
onSelect={props.onChange}
value={props.value}
options={historyOptions}
onSelect={onChange}
value={value}
placeholderEmpty={null}
timeZone={props.timeZone}
timeZone={timeZone}
/>
)}
</div>
......@@ -239,11 +252,7 @@ const NarrowScreenForm: React.FC<FormProps> = props => {
const FullScreenForm: React.FC<FormProps> = props => {
const theme = useTheme();
const styles = getFullScreenStyles(theme);
if (!props.visible) {
return null;
}
const styles = getFullScreenStyles(theme, props.hideQuickRanges);
return (
<>
......
......@@ -32,7 +32,7 @@ interface InputState {
const errorMessage = 'Please enter a past date or "now"';
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 [to, setTo] = useState<InputState>(valueToState(value.raw.to, true, timeZone));
......@@ -122,7 +122,7 @@ export const TimeRangeForm: React.FC<Props> = props => {
onClose={() => setOpen(false)}
onChange={onChange}
timeZone={timeZone}
isReversed={props.isReversed}
isReversed={isReversed}
/>
</>
);
......
// 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>
`;
import React from 'react';
import { LocalStorageValueProvider } from '../LocalStorageValueProvider';
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';
interface Props extends Omit<TimePickerProps, 'history' | 'theme'> {}
interface Props extends Omit<TimeRangePickerProps, 'history' | 'theme'> {}
export const TimePickerWithHistory: React.FC<Props> = props => {
return (
......
......@@ -5731,6 +5731,20 @@
lodash "^4.17.15"
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":
version "3.2.1"
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"
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:
version "0.63.5"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.63.5.tgz#5543eecac9a1b83b7e1be598b5be84c9c0a089db"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment