Skip to content

Commit 1598db8

Browse files
authored
feat: add timezone switcher (#270)
1 parent 833b8ef commit 1598db8

File tree

7 files changed

+513
-30
lines changed

7 files changed

+513
-30
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@
2525
"license": "MIT",
2626
"dependencies": {
2727
"@ant-design/icons": "^5.6.1",
28+
"@date-fns/tz": "^1.4.1",
2829
"antd": "^5.25.3",
2930
"chart.js": "3.5.0",
3031
"chartjs-adapter-date-fns": "^2.0.0",
3132
"chartjs-plugin-zoom": "^1.1.1",
3233
"classnames": "2.2.6",
33-
"date-fns": "^2.16.1",
34+
"date-fns": "4.1.0",
3435
"debug": "4.3.1",
3536
"electron-devtools-installer": "4.0.0",
3637
"electron-squirrel-startup": "1.0.0",
@@ -65,7 +66,6 @@
6566
"@testing-library/jest-dom": "^6.6.3",
6667
"@testing-library/react": "^12.1.2",
6768
"@types/classnames": "^2.2.11",
68-
"@types/date-fns": "^2.6.0",
6969
"@types/debug": "4.1.5",
7070
"@types/electron-squirrel-startup": "^1.0.2",
7171
"@types/fs-extra": "^9.0.4",

src/renderer/components/app-core-header-filter.tsx

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { observer } from 'mobx-react';
22
import React, { useEffect } from 'react';
33
import { debounce } from 'lodash';
4-
import dayjs, { Dayjs } from 'dayjs';
54
import {
65
Button,
76
DatePicker,
@@ -10,6 +9,8 @@ import {
109
Input,
1110
InputRef,
1211
Space,
12+
Switch,
13+
Tooltip,
1314
} from 'antd';
1415
import { SleuthState } from '../state/sleuth';
1516
import {
@@ -25,6 +26,8 @@ import {
2526
SearchOutlined,
2627
WarningOutlined,
2728
} from '@ant-design/icons';
29+
import { TZDate, tzOffset } from '@date-fns/tz';
30+
import dateFnsGenerateConfig from 'rc-picker/lib/generate/dateFns';
2831

2932
export interface FilterProps {
3033
state: SleuthState;
@@ -79,7 +82,7 @@ export const Filter = observer((props: FilterProps) => {
7982
}, 500);
8083

8184
const handleDateRangeChange = (
82-
values: [Dayjs, Dayjs],
85+
values: [TZDate, TZDate],
8386
dateStrings: [string, string],
8487
) => {
8588
props.state.dateRange = {
@@ -108,7 +111,9 @@ export const Filter = observer((props: FilterProps) => {
108111
/>
109112
);
110113

111-
const { RangePicker } = DatePicker;
114+
const { RangePicker } = DatePicker.generatePicker<Date>(
115+
dateFnsGenerateConfig,
116+
);
112117

113118
function renderFilter() {
114119
const { error, warn, info, debug } = props.state.levelFilter;
@@ -200,7 +205,6 @@ export const Filter = observer((props: FilterProps) => {
200205
key: 'reset',
201206
},
202207
],
203-
// onMouseLeave: (): void => setOpen(false),
204208
}}
205209
>
206210
<Button
@@ -210,17 +214,50 @@ export const Filter = observer((props: FilterProps) => {
210214
</Dropdown>
211215
);
212216
}
217+
218+
const systemTZ = Intl.DateTimeFormat().resolvedOptions().timeZone;
219+
const userTZ = props.state.stateFiles['log-context.json']?.data?.systemTZ;
220+
const tz = props.state.isUserTZ ? userTZ : systemTZ;
221+
const offset = tzOffset(tz, new Date('2020-01-15T00:00:00Z'));
222+
const isTZSwitchable = userTZ && userTZ !== systemTZ;
223+
213224
return (
214225
<Space className="SearchGroup">
226+
{!!userTZ && (
227+
<div>
228+
<Space>
229+
<Tooltip
230+
placement="right"
231+
title={`${tz} (UTC${offset < 0 ? '' : '+'}${offset / 60})`}
232+
>
233+
<Switch
234+
disabled={!isTZSwitchable}
235+
checkedChildren={
236+
isTZSwitchable
237+
? 'TZ: System'
238+
: `TZ: UTC${offset < 0 ? '' : '+'}${offset / 60}`
239+
}
240+
unCheckedChildren={'TZ: User'}
241+
checked={!props.state.isUserTZ}
242+
onChange={() => {
243+
props.state.toggleTZ();
244+
}}
245+
/>
246+
</Tooltip>
247+
</Space>
248+
</div>
249+
)}
215250
<RangePicker
216-
showTime={{
217-
defaultValue: [
218-
dayjs('00:00:00', 'HH:mm:ss'),
219-
dayjs('23:59:59', 'HH:mm:ss'),
220-
],
221-
}}
251+
showTime
222252
onChange={handleDateRangeChange}
223-
allowEmpty={[true, true]}
253+
value={[
254+
props.state.dateRange.from
255+
? new TZDate(props.state.dateRange.from, tz)
256+
: null,
257+
props.state.dateRange.to
258+
? new TZDate(props.state.dateRange.to, tz)
259+
: null,
260+
]}
224261
/>
225262
<Divider type="vertical" />
226263
<Space className="FilterGroup">

src/renderer/components/log-table-constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface LogTableState {
4343
sortDirection?: SORT_DIRECTION;
4444
ignoreSearchIndex?: boolean;
4545
scrollToSelection?: boolean;
46+
userTZ?: string;
4647
}
4748

4849
export interface SortFilterListOptions {

src/renderer/components/log-table.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import classNames from 'classnames';
33
import { format } from 'date-fns';
4+
import { TZDate } from '@date-fns/tz';
45
import { default as keydown } from 'react-keydown';
56
import autoBind from 'react-autobind';
67
import {
@@ -59,6 +60,8 @@ export const logColorMap: Record<ProcessableLogType, string> = {
5960
*/
6061
@observer
6162
export class LogTable extends React.Component<LogTableProps, LogTableState> {
63+
private tableRef = React.createRef<Table>();
64+
6265
constructor(props: LogTableProps) {
6366
super(props);
6467

@@ -67,10 +70,12 @@ export class LogTable extends React.Component<LogTableProps, LogTableState> {
6770
sortBy: 'index',
6871
sortDirection: props.state.defaultSort || SORT_DIRECTION.DESC,
6972
ignoreSearchIndex: false,
73+
userTZ: props.state.stateFiles['log-context.json']?.data?.systemTZ,
7074
};
7175

7276
autoBind(this);
7377
this.selectSearchIndex();
78+
this.setupTimezoneReaction();
7479
}
7580

7681
/**
@@ -89,6 +94,22 @@ export class LogTable extends React.Component<LogTableProps, LogTableState> {
8994
);
9095
}
9196

97+
/**
98+
* Sets up a reaction to force update the grid when timezone setting changes.
99+
*
100+
* @see https://github.com/bvaughn/react-virtualized/blob/master/docs/Table.md#forceupdategrid
101+
*/
102+
private setupTimezoneReaction() {
103+
reaction(
104+
() => this.props.state.isUserTZ,
105+
() => {
106+
if (this.tableRef.current) {
107+
this.tableRef.current.forceUpdateGrid();
108+
}
109+
},
110+
);
111+
}
112+
92113
public componentDidUpdate() {
93114
if (
94115
this.props.state.searchList.length > 0 &&
@@ -631,7 +652,13 @@ export class LogTable extends React.Component<LogTableProps, LogTableState> {
631652
}: TableCellProps): JSX.Element | string {
632653
const { dateTimeFormat } = this.props;
633654
const timestamp = entry.momentValue
634-
? format(entry.momentValue, dateTimeFormat)
655+
? format(
656+
new TZDate(
657+
entry.momentValue,
658+
this.props.state.isUserTZ ? this.state.userTZ : undefined,
659+
),
660+
dateTimeFormat,
661+
)
635662
: entry.timestamp;
636663
let prefix = <i className="Meta ts_icon ts_icon_question" />;
637664

@@ -705,7 +732,7 @@ export class LogTable extends React.Component<LogTableProps, LogTableState> {
705732
tableOptions.scrollToIndex = searchList[searchIndex] || 0;
706733

707734
return (
708-
<Table {...tableOptions}>
735+
<Table {...tableOptions} ref={this.tableRef}>
709736
<Column
710737
label="Index"
711738
dataKey="index"

src/renderer/state/sleuth.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export class SleuthState {
8181
@observable public isDetailsVisible = false;
8282
@observable public isSidebarOpen = true;
8383
@observable public isSpotlightOpen = false;
84+
@observable public isUserTZ = false;
8485
@observable.shallow public bookmarks: Array<Bookmark> = [];
8586
@observable public serializedBookmarks: Record<
8687
string,
@@ -255,6 +256,11 @@ export class SleuthState {
255256
this.isSpotlightOpen = !this.isSpotlightOpen;
256257
}
257258

259+
@action
260+
public toggleTZ() {
261+
this.isUserTZ = !this.isUserTZ;
262+
}
263+
258264
@action
259265
public async getSuggestions(suggestions?: Suggestion[]) {
260266
this.suggestions = suggestions || (await window.Sleuth.getSuggestions());
@@ -301,6 +307,7 @@ export class SleuthState {
301307
this.isDetailsVisible = false;
302308
this.dateRange = { from: null, to: null };
303309
this.traceThreads = undefined;
310+
this.stateFiles = {};
304311

305312
if (goBackToHome) {
306313
this.resetApp();

0 commit comments

Comments
 (0)