Skip to content

Commit f4f3239

Browse files
committed
refactor: normalizing dates in ui now happens in RelativeDateTimeSelector component
1 parent 0f7c7dc commit f4f3239

File tree

8 files changed

+60
-61
lines changed

8 files changed

+60
-61
lines changed

ui/src/common/RelativeDateTimeSelector.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react';
22
import {TextField} from '@material-ui/core';
3-
import {parseRelativeTime} from '../utils/time';
3+
import {normalizeDate, parseRelativeTime, userFriendlyDate} from '../utils/time';
44
import Typography from '@material-ui/core/Typography';
55
import useTimeout from '@rooks/use-timeout';
66

@@ -16,7 +16,7 @@ interface RelativeDateTimeSelectorProps {
1616
}
1717

1818
export const RelativeDateTimeSelector: React.FC<RelativeDateTimeSelectorProps> = ({
19-
value,
19+
value: apiValue,
2020
onChange: setValue,
2121
type,
2222
style,
@@ -28,8 +28,9 @@ export const RelativeDateTimeSelector: React.FC<RelativeDateTimeSelectorProps> =
2828
const [errVisible, setErrVisible] = React.useState(false);
2929
const [error, setError] = React.useState('');
3030
const {start, stop} = useTimeout(() => setErrVisible(true), 200);
31+
const parsed = parseRelativeTime(apiValue, type);
32+
const value = userFriendlyDate(apiValue);
3133

32-
const parsed = parseRelativeTime(value, type);
3334
return (
3435
<TextField
3536
fullWidth
@@ -38,7 +39,7 @@ export const RelativeDateTimeSelector: React.FC<RelativeDateTimeSelectorProps> =
3839
disabled={disabled}
3940
InputProps={{disableUnderline}}
4041
onChange={(e) => {
41-
const newValue = e.target.value;
42+
const newValue = normalizeDate(e.target.value);
4243
const result = parseRelativeTime(newValue, type);
4344
setErrVisible(false);
4445
stop();

ui/src/dashboard/Entry/AddPopup.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import * as gqlDashboard from '../../gql/dashboard';
1010
import {Fade} from '../../common/Fade';
1111
import {DashboardEntryForm, isValidDashboardEntry} from './DashboardEntryForm';
1212
import {AddDashboardEntry, AddDashboardEntryVariables} from '../../gql/__generated__/AddDashboardEntry';
13-
import {normalizeRangeDateFormat} from '../../utils/range';
1413

1514
interface EditPopupProps {
1615
dashboardId: number;
@@ -84,10 +83,10 @@ export const AddPopup: React.FC<EditPopupProps> = ({
8483
tags: entry.statsSelection.tags,
8584
interval: entry.statsSelection.interval,
8685
range: entry.statsSelection.range
87-
? normalizeRangeDateFormat({
86+
? {
8887
from: entry.statsSelection.range.from,
8988
to: entry.statsSelection.range.to,
90-
})
89+
}
9190
: null,
9291
rangeId: entry.statsSelection.rangeId,
9392
},

ui/src/dashboard/Entry/DashboardEntry.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {DashboardBarChart} from './DashboardBarChart';
1414
import {DashboardLineChart} from './DashboardLineChart';
1515
import {CenteredSpinner} from '../../common/CenteredSpinner';
1616
import {Center} from '../../common/Center';
17-
import {findRange, normalizeRangeDateFormat, Range} from '../../utils/range';
17+
import {findRange, Range} from '../../utils/range';
1818
import {DashboardTable} from './DashboardTable';
1919

2020
interface DashboardEntryProps {
@@ -43,14 +43,13 @@ export const DashboardEntry: React.FC<DashboardEntryProps> = React.forwardRef<{}
4343
// tslint:disable-next-line:cyclomatic-complexity mccabe-complexity
4444
const SpecificDashboardEntry: React.FC<{entry: Dashboards_dashboards_items; range: Range}> = ({entry, range}) => {
4545
const interval = entry.statsSelection.interval;
46-
const normalizedRange = normalizeRangeDateFormat(range);
4746
const stats = useQuery<Stats2, Stats2Variables>(gqlStats.Stats2, {
4847
variables: {
4948
now: moment()
5049
.startOf('hour')
5150
.format(),
5251
stats: {
53-
range: normalizedRange,
52+
range,
5453
interval,
5554
tags: entry.statsSelection.tags,
5655
},

ui/src/dashboard/Entry/EditPopup.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import * as gqlDashboard from '../../gql/dashboard';
1010
import {UpdateDashboardEntry, UpdateDashboardEntryVariables} from '../../gql/__generated__/UpdateDashboardEntry';
1111
import {Fade} from '../../common/Fade';
1212
import {DashboardEntryForm, isValidDashboardEntry} from './DashboardEntryForm';
13-
import {normalizeRangeDateFormat} from '../../utils/range';
1413

1514
interface EditPopupProps {
1615
entry: Dashboards_dashboards_items;
@@ -70,10 +69,10 @@ export const EditPopup: React.FC<EditPopupProps> = ({entry, anchorEl, onChange:
7069
tags: entry.statsSelection.tags,
7170
interval: entry.statsSelection.interval,
7271
range: entry.statsSelection.range
73-
? normalizeRangeDateFormat({
72+
? {
7473
from: entry.statsSelection.range.from,
7574
to: entry.statsSelection.range.to,
76-
})
75+
}
7776
: null,
7877
rangeId: entry.statsSelection.rangeId,
7978
},

ui/src/utils/range.test.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.

ui/src/utils/range.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import moment from 'moment-timezone';
2-
31
export interface Range {
42
from: string;
53
to: string;
@@ -16,16 +14,3 @@ export const findRange = (selection: {range: Range | null; rangeId: number | nul
1614
};
1715

1816
export const exclusiveRange = (range: Range) => ({from: range.from, to: range.to});
19-
20-
export function normalizeDate(date: string): string {
21-
const d = moment(date);
22-
if (d.isValid()) {
23-
return d.format();
24-
} else {
25-
return date;
26-
}
27-
}
28-
29-
export function normalizeRangeDateFormat(range: Range): Range {
30-
return {from: normalizeDate(range.from), to: normalizeDate(range.to)};
31-
}

ui/src/utils/time.test.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {isValidDate, parseRelativeTime} from './time';
1+
import {isValidDate, normalizeDate, parseRelativeTime, userFriendlyDate} from './time';
22
import moment from 'moment';
33

44
moment.updateLocale('en', {
@@ -9,9 +9,12 @@ moment.updateLocale('en', {
99
});
1010

1111
it('should test for valid date', () => {
12-
expect(isValidDate('2017-05-05')).toBe(false);
13-
expect(isValidDate('2017-05-05T15:23')).toBe(false);
14-
expect(isValidDate('2017-05-05 15:23')).toBe(true);
12+
expect(isValidDate('2017-05-05', 'YYYY-MM-DD HH:mm')).toBe(false);
13+
expect(isValidDate('2017-05-05T15:23', 'YYYY-MM-DD HH:mm')).toBe(false);
14+
expect(isValidDate('2017-05-05 15:23', 'YYYY-MM-DD HH:mm')).toBe(true);
15+
16+
expect(isValidDate('2019-10-20T15:55:00Z')).toBe(true);
17+
expect(isValidDate('2017-05-05 15:23')).toBe(false);
1518
});
1619

1720
// 2018-10-15 Monday
@@ -33,6 +36,27 @@ it('should parse', () => {
3336
expectSuccess(parseRelativeTime('now/y', 'startOf', moment('2019-10-20T15:55:15'))).toEqual('2019-01-01 00:00:00');
3437
});
3538

39+
it('should convert to RFC3339 and back', () => {
40+
// can't put exact dates since moment doesn't allow overriding `.local()`'s timezone for unit tests
41+
const userDate = '2025-01-01 10:10';
42+
const rfcDate = moment(userDate).utc().format();
43+
44+
expect(normalizeDate(userDate)).toBe(rfcDate);
45+
expect(userFriendlyDate(rfcDate)).toBe(userDate);
46+
});
47+
48+
it('should not modify relative ranges', () => {
49+
expect(normalizeDate('now-1d')).toBe('now-1d');
50+
expect(normalizeDate('now-120s')).toBe('now-120s');
51+
expect(normalizeDate('now-1d-1h')).toBe('now-1d-1h');
52+
expect(normalizeDate('now/w')).toBe('now/w');
53+
expect(normalizeDate('now/w')).toBe('now/w');
54+
expect(normalizeDate('now-1w/w')).toBe('now-1w/w');
55+
expect(normalizeDate('now-1y+1w/w')).toBe('now-1y+1w/w');
56+
expect(normalizeDate('now/d+5h')).toBe('now/d+5h');
57+
expect(normalizeDate('now/y')).toBe('now/y');
58+
});
59+
3660
const expectSuccess = (value: ReturnType<typeof parseRelativeTime>) => {
3761
if (value.success) {
3862
return expect(value.value.format('YYYY-MM-DD HH:mm:ss'));

ui/src/utils/time.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import moment from 'moment';
1+
import moment from 'moment-timezone';
22

33
interface Success {
44
success: true;
@@ -112,7 +112,7 @@ export const parseRelativeTime = (value: string, divide: 'endOf' | 'startOf', no
112112
return failure("'now' must be at the start");
113113
}
114114

115-
return failure("Expected valid date or 'now' at index 0");
115+
return failure("Expected valid date (e.g. 2020-01-01 16:30) or 'now' at index 0");
116116
};
117117

118118
export const success = (value: moment.Moment): Success => {
@@ -133,10 +133,28 @@ export const isValidDate = (value: string, format?: string) => {
133133
return asDate(value, format).isValid();
134134
};
135135

136-
export const asDate = (value: string, format = 'YYYY-MM-DD HH:mm') => {
136+
export const asDate = (value: string, format = 'YYYY-MM-DD[T]HH:mm:ssZ') => {
137137
return moment(value, format, true);
138138
};
139139
export const isSameDate = (from: moment.Moment, to?: moment.Moment): boolean => {
140140
const fromString = from.format('YYYYMMDD');
141141
return to === undefined || fromString === to.format('YYYYMMDD');
142142
};
143+
144+
export function normalizeDate(date: string): string {
145+
if (isValidDate(date, 'YYYY-MM-DD HH:mm')) {
146+
return moment(date)
147+
.utc()
148+
.format();
149+
} else {
150+
return date;
151+
}
152+
}
153+
154+
export function userFriendlyDate(date: string): string {
155+
if (isValidDate(date)) {
156+
return moment(date).local().format('YYYY-MM-DD HH:mm');
157+
} else {
158+
return date;
159+
}
160+
}

0 commit comments

Comments
 (0)