mirror of
https://github.com/grafana/grafana.git
synced 2025-07-25 16:33:51 +08:00
TimePicker: Fixes issues with "Recently used absolute ranges" section (#66281)
* TimePicker: Fixes issues with "Recently used absolute ranges" section Squashed commit of the following: commit 99d5076ce1fadde1f22ed372f372656b58efa1c4 Author: Joao Silva <joao.silva@grafana.com> Date: Tue Apr 11 14:06:27 2023 +0100 user essentials mob! 🔱 lastFile:public/app/core/components/TimePicker/TimePickerWithHistory.tsx commit cad0201df452f956a422b030d5b15e8ba4aed9a9 Author: eledobleefe <laura.fernandez@grafana.com> Date: Tue Apr 11 11:44:34 2023 +0200 user essentials mob! 🔱 lastFile:public/app/core/components/TimePicker/TimePickerWithHistory.tsx Co-authored-by: eledobleefe <laura.fernandez@grafana.com> * TimePicker: Add correct date format * Add convertRawToRange tests * Rename test variables * RTL tests * Proper RTL tests * Apply suggestions from code review Co-authored-by: Joao Silva <100691367+JoaoSilvaGrafana@users.noreply.github.com> * Remove commented line * Fix linting --------- Co-authored-by: eledobleefe <laura.fernandez@grafana.com> Co-authored-by: Tobias Skarhed <tobias.skarhed@gmail.com> Co-authored-by: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com>
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
// We set this specifically for 2 reasons.
|
||||
// 1. It makes sense for both CI tests and local tests to behave the same so issues are found earlier
|
||||
// 2. Any wrong timezone handling could be hidden if we use UTC/GMT local time (which would happen in CI).
|
||||
process.env.TZ = 'Pacific/Easter';
|
||||
process.env.TZ = 'Pacific/Easter'; // UTC-06:00 or UTC-05:00 depending on daylight savings
|
||||
|
||||
const esModules = ['ol', 'd3', 'd3-color', 'd3-interpolate', 'delaunator', 'internmap', 'robust-predicates'].join('|');
|
||||
|
||||
|
@ -2,6 +2,11 @@ import { systemDateFormats, SystemDateFormatsState } from './formats';
|
||||
import { dateTimeParse } from './parser';
|
||||
|
||||
describe('dateTimeParse', () => {
|
||||
it('should parse using the systems configured timezone', () => {
|
||||
const date = dateTimeParse('2020-03-02 15:00:22');
|
||||
expect(date.format()).toEqual('2020-03-02T15:00:22-05:00');
|
||||
});
|
||||
|
||||
it('should be able to parse using default format', () => {
|
||||
const date = dateTimeParse('2020-03-02 15:00:22', { timeZone: 'utc' });
|
||||
expect(date.format()).toEqual('2020-03-02T15:00:22Z');
|
||||
|
@ -1,10 +1,57 @@
|
||||
import { TimeRange } from '../types/time';
|
||||
import { RawTimeRange, TimeRange } from '../types/time';
|
||||
|
||||
import { timeRangeToRelative } from './rangeutil';
|
||||
|
||||
import { dateTime, rangeUtil } from './index';
|
||||
|
||||
describe('Range Utils', () => {
|
||||
// These tests probably wrap the dateTimeParser tests to some extent
|
||||
describe('convertRawToRange', () => {
|
||||
const DEFAULT_DATE_VALUE = '1996-07-30 16:00:00'; // Default format YYYY-MM-DD HH:mm:ss
|
||||
const DEFAULT_DATE_VALUE_FORMATTED = '1996-07-30T16:00:00-06:00';
|
||||
const defaultRawTimeRange = {
|
||||
from: DEFAULT_DATE_VALUE,
|
||||
to: '1996-07-30 16:20:00',
|
||||
};
|
||||
|
||||
it('should serialize the default format by default', () => {
|
||||
const deserialized = rangeUtil.convertRawToRange(defaultRawTimeRange);
|
||||
expect(deserialized.from.format()).toBe(DEFAULT_DATE_VALUE_FORMATTED);
|
||||
});
|
||||
|
||||
it('should serialize using custom formats', () => {
|
||||
const NON_DEFAULT_FORMAT = 'DD-MM-YYYY HH:mm:ss';
|
||||
const nonDefaultRawTimeRange: RawTimeRange = {
|
||||
from: '30-07-1996 16:00:00',
|
||||
to: '30-07-1996 16:20:00',
|
||||
};
|
||||
|
||||
const deserializedTimeRange = rangeUtil.convertRawToRange(
|
||||
nonDefaultRawTimeRange,
|
||||
undefined,
|
||||
undefined,
|
||||
NON_DEFAULT_FORMAT
|
||||
);
|
||||
expect(deserializedTimeRange.from.format()).toBe(DEFAULT_DATE_VALUE_FORMATTED);
|
||||
});
|
||||
|
||||
it('should take timezone into account', () => {
|
||||
const deserializedTimeRange = rangeUtil.convertRawToRange(defaultRawTimeRange, 'UTC');
|
||||
expect(deserializedTimeRange.from.format()).toBe('1996-07-30T16:00:00Z');
|
||||
});
|
||||
|
||||
it('should leave the raw part intact if it has calulactions', () => {
|
||||
const timeRange = {
|
||||
from: DEFAULT_DATE_VALUE,
|
||||
to: 'now',
|
||||
};
|
||||
|
||||
const deserialized = rangeUtil.convertRawToRange(timeRange);
|
||||
expect(deserialized.raw).toStrictEqual(timeRange);
|
||||
expect(deserialized.to.toString()).not.toBe(deserialized.raw.to);
|
||||
});
|
||||
});
|
||||
|
||||
describe('relative time', () => {
|
||||
it('should identify absolute vs relative', () => {
|
||||
expect(
|
||||
|
@ -198,9 +198,14 @@ export const describeTimeRangeAbbreviation = (range: TimeRange, timeZone?: TimeZ
|
||||
return parsed ? timeZoneAbbrevation(parsed, { timeZone }) : '';
|
||||
};
|
||||
|
||||
export const convertRawToRange = (raw: RawTimeRange, timeZone?: TimeZone, fiscalYearStartMonth?: number): TimeRange => {
|
||||
const from = dateTimeParse(raw.from, { roundUp: false, timeZone, fiscalYearStartMonth });
|
||||
const to = dateTimeParse(raw.to, { roundUp: true, timeZone, fiscalYearStartMonth });
|
||||
export const convertRawToRange = (
|
||||
raw: RawTimeRange,
|
||||
timeZone?: TimeZone,
|
||||
fiscalYearStartMonth?: number,
|
||||
format?: string
|
||||
): TimeRange => {
|
||||
const from = dateTimeParse(raw.from, { roundUp: false, timeZone, fiscalYearStartMonth, format });
|
||||
const to = dateTimeParse(raw.to, { roundUp: true, timeZone, fiscalYearStartMonth, format });
|
||||
|
||||
if (dateMath.isMathString(raw.from) || dateMath.isMathString(raw.to)) {
|
||||
return { from, to, raw };
|
||||
|
@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { getDefaultTimeRange } from '@grafana/data';
|
||||
import { getDefaultTimeRange, systemDateFormats } from '@grafana/data';
|
||||
|
||||
import { TimePickerWithHistory } from './TimePickerWithHistory';
|
||||
|
||||
@ -41,7 +41,9 @@ describe('TimePickerWithHistory', () => {
|
||||
onZoom: () => {},
|
||||
};
|
||||
|
||||
afterEach(() => window.localStorage.clear());
|
||||
afterEach(() => {
|
||||
window.localStorage.clear();
|
||||
});
|
||||
|
||||
it('Should load with no history', async () => {
|
||||
const timeRange = getDefaultTimeRange();
|
||||
@ -124,6 +126,50 @@ describe('TimePickerWithHistory', () => {
|
||||
const newLsValue = JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_KEY) ?? '[]');
|
||||
expect(newLsValue).toEqual(expectedLocalStorage);
|
||||
});
|
||||
|
||||
it('Should display handle timezones correctly', async () => {
|
||||
const timeRange = getDefaultTimeRange();
|
||||
render(<TimePickerWithHistory value={timeRange} {...props} {...{ timeZone: 'Eastern/Pacific' }} />);
|
||||
await userEvent.click(screen.getByLabelText(/Time range selected/));
|
||||
|
||||
await clearAndType(getFromField(), '2022-12-10 00:00:00');
|
||||
await clearAndType(getToField(), '2022-12-10 23:59:59');
|
||||
await userEvent.click(getApplyButton());
|
||||
|
||||
await userEvent.click(screen.getByLabelText(/Time range selected/));
|
||||
|
||||
expect(screen.getByText(/2022-12-10 00:00:00 to 2022-12-10 23:59:59/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should display history correctly with custom time format', async () => {
|
||||
const timeRange = getDefaultTimeRange();
|
||||
|
||||
const interval = {
|
||||
millisecond: 'HH:mm:ss.SSS',
|
||||
second: 'HH:mm:ss',
|
||||
minute: 'HH:mm',
|
||||
hour: 'DD-MM HH:mm',
|
||||
day: 'DD-MM',
|
||||
month: 'MM-YYYY',
|
||||
year: 'YYYY',
|
||||
};
|
||||
|
||||
systemDateFormats.update({
|
||||
fullDate: 'DD-MM-YYYY HH:mm:ss',
|
||||
interval: interval,
|
||||
useBrowserLocale: false,
|
||||
});
|
||||
render(<TimePickerWithHistory value={timeRange} {...props} />);
|
||||
await userEvent.click(screen.getByLabelText(/Time range selected/));
|
||||
|
||||
await clearAndType(getFromField(), '03-12-2022 00:00:00');
|
||||
await clearAndType(getToField(), '03-12-2022 23:59:59');
|
||||
await userEvent.click(getApplyButton());
|
||||
|
||||
await userEvent.click(screen.getByLabelText(/Time range selected/));
|
||||
|
||||
expect(screen.getByText(/03-12-2022 00:00:00 to 03-12-2022 23:59:59/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
async function clearAndType(field: HTMLElement, text: string) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { uniqBy } from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
import { TimeRange, isDateTime, rangeUtil, TimeZone } from '@grafana/data';
|
||||
import { TimeRange, isDateTime, rangeUtil } from '@grafana/data';
|
||||
import { TimeRangePickerProps, TimeRangePicker } from '@grafana/ui';
|
||||
|
||||
import { LocalStorageValueProvider } from '../LocalStorageValueProvider';
|
||||
@ -24,7 +24,7 @@ export const TimePickerWithHistory = (props: Props) => {
|
||||
<LocalStorageValueProvider<LSTimePickerHistoryItem[]> storageKey={LOCAL_STORAGE_KEY} defaultValue={[]}>
|
||||
{(rawValues, onSaveToStore) => {
|
||||
const values = migrateHistory(rawValues);
|
||||
const history = deserializeHistory(values, props.timeZone);
|
||||
const history = deserializeHistory(values);
|
||||
|
||||
return (
|
||||
<TimeRangePicker
|
||||
@ -41,8 +41,9 @@ export const TimePickerWithHistory = (props: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
function deserializeHistory(values: TimePickerHistoryItem[], timeZone: TimeZone | undefined): TimeRange[] {
|
||||
return values.map((item) => rangeUtil.convertRawToRange(item, timeZone));
|
||||
function deserializeHistory(values: TimePickerHistoryItem[]): TimeRange[] {
|
||||
// The history is saved in UTC and with the default date format, so we need to pass those values to the convertRawToRange
|
||||
return values.map((item) => rangeUtil.convertRawToRange(item, 'utc', undefined, 'YYYY-MM-DD HH:mm:ss'));
|
||||
}
|
||||
|
||||
function migrateHistory(values: LSTimePickerHistoryItem[]): TimePickerHistoryItem[] {
|
||||
|
Reference in New Issue
Block a user