Skip to content

Commit bb9abc2

Browse files
authored
NETOBSERV-526 Display milliseconds for time fields in NetFlow table (#205)
* record field date format * set moment locale + force 24H format * Intl.DateTimeFormat + styling * removed moment dependency * removed weekday from side panel
1 parent 53b1d73 commit bb9abc2

File tree

9 files changed

+108
-65
lines changed

9 files changed

+108
-65
lines changed

web/package-lock.json

Lines changed: 7 additions & 43 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
"@types/jest": "27.0.3",
3939
"@types/jsdom": "^16.2.13",
4040
"@types/lodash": "^4.14.178",
41-
"@types/moment": "^2.13.0",
4241
"@types/port-numbers": "^5.0.0",
4342
"@types/react": "^17.0.1",
4443
"@types/react-dom": "^17.0.1",
@@ -67,7 +66,6 @@
6766
"jsdom": "19.0.0",
6867
"jsdom-global": "3.0.2",
6968
"mini-css-extract-plugin": "^2.6.0",
70-
"moment": "^2.29.4",
7169
"prettier": "^2.5.1",
7270
"pretty-quick": "^3.1.2",
7371
"react-transition-group": "^4.4.2",
@@ -76,7 +74,7 @@
7674
"ts-jest-resolver": "2.0.0",
7775
"ts-loader": "6.2.2",
7876
"ts-node": "5.0.1",
79-
"typescript": "3.8.3",
77+
"typescript": "4.2.3",
8078
"webpack": "^5.62.1",
8179
"webpack-cli": "^4.9.1",
8280
"webpack-dev-server": "^4.6.0",

web/src/components/netflow-record/record-field.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,14 @@
8888

8989
.child-arrow {
9090
margin: 0 5px 0 5px;
91+
}
92+
93+
.record-field-date {
94+
display: flex;
95+
align-items: baseline;
96+
}
97+
98+
.record-field-date-icon {
99+
flex-shrink: 0;
100+
margin-right: 5px;
91101
}

web/src/components/netflow-record/record-field.tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { ResourceIcon, ResourceLink } from '@openshift-console/dynamic-plugin-sdk';
22
import { Button, Tooltip } from '@patternfly/react-core';
3-
import { FilterIcon, TimesIcon } from '@patternfly/react-icons';
3+
import { FilterIcon, GlobeAmericasIcon, TimesIcon } from '@patternfly/react-icons';
44
import * as React from 'react';
55
import { useTranslation } from 'react-i18next';
66
import { FlowDirection, Record } from '../../api/ipfix';
77
import { Column, ColumnsId, getFullColumnName } from '../../utils/columns';
8+
import { dateFormatter, getFormattedDate, timeMSFormatter, utcDateTimeFormatter } from '../../utils/datetime';
89
import { formatDurationAboveMillisecond } from '../../utils/duration';
910
import { formatPort } from '../../utils/port';
1011
import { formatProtocol } from '../../utils/protocol';
@@ -122,14 +123,27 @@ export const RecordField: React.FC<{
122123
};
123124

124125
const dateTimeContent = (date: Date | undefined) => {
125-
const dateText = date?.toDateString() || emptyText();
126-
const timeText = date?.toLocaleTimeString() || emptyText();
126+
if (!date) {
127+
return emptyText();
128+
}
129+
130+
const fullDateText = getFormattedDate(date, utcDateTimeFormatter);
131+
const dateText = getFormattedDate(date, dateFormatter) + ',';
132+
const timeText = getFormattedDate(date, timeMSFormatter);
127133
return singleContainer(
128-
<div data-test={`field-date-${dateText}-${timeText}`}>
129-
<div className={`datetime ${size}`}>
130-
<span>{dateText}</span> <span className="text-muted">{timeText}</span>
131-
</div>
132-
<div className="record-field-tooltip">{`${dateText} ${timeText}`}</div>
134+
<div data-test={`field-date-${dateText}-${timeText}`} className="record-field-date">
135+
<GlobeAmericasIcon className="record-field-date-icon" />
136+
<Tooltip
137+
content={[
138+
<span className="co-nowrap" key="co-timestamp">
139+
{fullDateText}
140+
</span>
141+
]}
142+
>
143+
<div className={`datetime ${size}`}>
144+
<span>{dateText}</span> <span className="text-muted">{timeText}</span>
145+
</div>
146+
</Tooltip>
133147
</div>
134148
);
135149
};

web/src/components/netflow-table/__tests__/netflow-table.spec.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { FlowsMock, FlowsSample } from '../../__tests-data__/flows';
1111
import { Size } from '../../dropdowns/display-dropdown';
1212
import { ColumnsId } from '../../../utils/columns';
1313
import { LokiError } from '../../messages/loki-error';
14+
import { dateTimeMSFormatter, getFormattedDate } from '../../../utils/datetime';
1415

1516
const errorStateQuery = `EmptyState[data-test="error-state"]`;
1617
const loadingContentsQuery = `Bullseye[data-test="loading-contents"]`;
@@ -70,7 +71,7 @@ describe('<NetflowTable />', () => {
7071
let expectedDate = new Date(FlowsSample[2].fields.TimeFlowEndMs);
7172
// table should be sorted by date asc by default
7273
expect(wrapper.find(NetflowTableRow).find(Td).at(timestampIdx).find('.datetime').at(0).text()).toBe(
73-
expectedDate.toDateString() + ' ' + expectedDate.toLocaleTimeString()
74+
getFormattedDate(expectedDate, dateTimeMSFormatter)
7475
);
7576

7677
const button = wrapper.findWhere(node => {
@@ -80,7 +81,7 @@ describe('<NetflowTable />', () => {
8081
expectedDate = new Date(FlowsSample[1].fields.TimeFlowEndMs);
8182
// then should sort date desc on click
8283
expect(wrapper.find(NetflowTableRow).find(Td).at(timestampIdx).find('.datetime').at(0).text()).toBe(
83-
expectedDate.toDateString() + ' ' + expectedDate.toLocaleTimeString()
84+
getFormattedDate(expectedDate, dateTimeMSFormatter)
8485
);
8586

8687
const expectedSrcAddress = FlowsSample[1].fields.SrcAddr;

web/src/index.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import React from 'react';
22
import ReactDOM from 'react-dom';
33

44
import i18n from 'i18next';
5-
import { initReactI18next } from 'react-i18next';
65
import httpBackend from 'i18next-http-backend';
6+
import { initReactI18next } from 'react-i18next';
77

8-
import '@patternfly/react-core/dist/styles/base.css';
9-
import '@patternfly/patternfly/patternfly-theme-dark.css';
108
import '@patternfly/patternfly/patternfly-charts-theme-dark.css';
9+
import '@patternfly/patternfly/patternfly-theme-dark.css';
10+
import '@patternfly/react-core/dist/styles/base.css';
1111

1212
import App from './app';
13+
import { getLanguage } from './utils/language';
1314
import './index.css';
1415

1516
//init standalone i18n translations
@@ -25,7 +26,7 @@ i18n
2526
backend: {
2627
loadPath: '/locales/{{lng}}/{{ns}}.json'
2728
},
28-
lng: 'en',
29+
lng: getLanguage(),
2930
fallbackLng: 'en',
3031
load: 'languageOnly',
3132
debug: process.env.NODE_ENV === 'development',

web/src/utils/datetime.ts

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { TFunction } from 'i18next';
2-
import moment from 'moment';
32
import { CUSTOM_TIME_RANGE_KEY } from '../components/dropdowns/time-range-dropdown';
3+
import { getLanguage } from './language';
44

55
const zeroPad = (number: number) => (number < 10 ? `0${number}` : number);
66

@@ -41,8 +41,53 @@ export const getTimeRangeOptions = (t: TFunction) => {
4141
};
4242
};
4343

44-
export const getFormattedDate = (date: Date, format = 'llll') => {
45-
return moment(date).format(format);
44+
export const dateFormatter = new Intl.DateTimeFormat(getLanguage(), {
45+
month: 'short',
46+
day: 'numeric',
47+
year: 'numeric'
48+
});
49+
50+
export const timeMSFormatter = new Intl.DateTimeFormat(getLanguage(), {
51+
hour: 'numeric',
52+
minute: 'numeric',
53+
second: 'numeric',
54+
fractionalSecondDigits: 3
55+
});
56+
57+
export const dateTimeFormatter = new Intl.DateTimeFormat(getLanguage(), {
58+
month: 'short',
59+
day: 'numeric',
60+
hour: 'numeric',
61+
minute: 'numeric',
62+
second: 'numeric',
63+
year: 'numeric'
64+
});
65+
66+
export const dateTimeMSFormatter = new Intl.DateTimeFormat(getLanguage(), {
67+
month: 'short',
68+
day: 'numeric',
69+
hour: 'numeric',
70+
minute: 'numeric',
71+
second: 'numeric',
72+
fractionalSecondDigits: 3,
73+
year: 'numeric'
74+
});
75+
76+
export const utcDateTimeFormatter = new Intl.DateTimeFormat(getLanguage(), {
77+
weekday: 'short',
78+
month: 'short',
79+
day: 'numeric',
80+
hour: 'numeric',
81+
minute: 'numeric',
82+
second: 'numeric',
83+
fractionalSecondDigits: 3,
84+
year: 'numeric',
85+
timeZone: 'UTC',
86+
timeZoneName: 'short'
87+
});
88+
89+
export const getFormattedDate = (date: Date, format = dateTimeFormatter) => {
90+
return format.format(date);
4691
};
4792

4893
export const rangeToSeconds = (range: TimeRange | number): number => {

web/src/utils/language.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const getLanguage = () => {
2+
return navigator.language || 'en';
3+
};

web/tsconfig.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
"module": "esnext",
77
"moduleResolution": "node",
88
"target": "es2020",
9+
"lib": [
10+
"dom",
11+
"dom.iterable",
12+
"esnext"
13+
],
914
"jsx": "react",
1015
"allowJs": true,
1116
"experimentalDecorators": true,
@@ -14,5 +19,7 @@
1419
"skipLibCheck": true,
1520
"strictNullChecks": true
1621
},
17-
"include": ["src"]
18-
}
22+
"include": [
23+
"src"
24+
]
25+
}

0 commit comments

Comments
 (0)