Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Customize 24h vs 12h time formatting globally #63

Merged
merged 18 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/three-forks-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@hyperdx/api': minor
'@hyperdx/app': minor
---

feat: time format ui addition
12 changes: 9 additions & 3 deletions packages/app/src/HDXLineChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import Link from 'next/link';
import pick from 'lodash/pick';

import { AggFn, Granularity, convertGranularityToSeconds } from './ChartUtils';
import { semanticKeyedColor, truncateMiddle } from './utils';
import { semanticKeyedColor, truncateMiddle, TIME_TOKENS } from './utils';
import useUserPreferences, { TimeFormat } from './useUserPreferences';

import api from './api';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extra new line

function ExpandableLegendItem({ value, entry }: any) {
Expand Down Expand Up @@ -88,6 +90,9 @@ const MemoChart = memo(function MemoChart({
}, [groupKeys, displayType]);

const sizeRef = useRef<[number, number]>([0, 0]);
const timeFormat: TimeFormat = useUserPreferences().timeFormat;
const tsFormat = TIME_TOKENS[timeFormat];
// Gets the preffered time format from User Preferences, then converts it to a formattable token

return (
<ResponsiveContainer
Expand Down Expand Up @@ -136,7 +141,7 @@ const MemoChart = memo(function MemoChart({
interval="preserveStartEnd"
scale="time"
type="number"
tickFormatter={tick => format(new Date(tick * 1000), 'MMM d HH:mm')}
tickFormatter={tick => format(new Date(tick * 1000), tsFormat)}
minTickGap={50}
tick={{ fontSize: 12, fontFamily: 'IBM Plex Mono, monospace' }}
/>
Expand Down Expand Up @@ -193,7 +198,8 @@ const MemoChart = memo(function MemoChart({
});

const HDXLineChartTooltip = (props: any) => {
const tsFormat = 'MMM d HH:mm:ss.SSS';
const timeFormat: TimeFormat = useUserPreferences().timeFormat;
const tsFormat = TIME_TOKENS[timeFormat];
const { active, payload, label } = props;
if (active && payload && payload.length) {
return (
Expand Down
6 changes: 5 additions & 1 deletion packages/app/src/LogTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import api from './api';
import { useLocalStorage, usePrevious, useWindowSize } from './utils';
import { useSearchEventStream } from './search';
import { useHotkeys } from 'react-hotkeys-hook';
import { TIME_TOKENS } from './utils';
import useUserPreferences from './useUserPreferences';
import type { TimeFormat } from './useUserPreferences';
import { UNDEFINED_WIDTH } from './tableUtils';

import styles from '../styles/LogTable.module.scss';
Expand Down Expand Up @@ -273,12 +276,13 @@ export const RawLogTable = memo(

const { width } = useWindowSize();
const isSmallScreen = (width ?? 1000) < 900;
const timeFormat: TimeFormat = useUserPreferences().timeFormat;
const tsFormat = TIME_TOKENS[timeFormat];

const [columnSizeStorage, setColumnSizeStorage] = useLocalStorage<
Record<string, number>
>(`${tableId}-column-sizes`, {});

const tsFormat = 'MMM d HH:mm:ss.SSS';
const tsShortFormat = 'HH:mm:ss';
//once the user has scrolled within 500px of the bottom of the table, fetch more data if there is any
const FETCH_NEXT_PAGE_PX = 500;
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/SearchTimeRangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default function SearchTimeRangePicker({
setInputValue,
onSearch,
showLive = false,
timeFormat = '24h',
timeFormat = '12h',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor thing - it looks like we end up overwriting this in the UI via

const formatDate = (
(so after you hit search for the time range, we end up showing 24h time again instead of 12h)

That being said, I don't think it's a huge deal, and something we can probably clean up once #75 lands since it might conflict anyways.

}: {
inputValue: string;
setInputValue: (str: string) => any;
Expand Down
43 changes: 43 additions & 0 deletions packages/app/src/TeamPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Link from 'next/link';
import {
Badge,
Button,
ToggleButton,
ToggleButtonGroup,
Container,
Form,
Modal,
Expand All @@ -13,6 +15,8 @@ import { CopyToClipboard } from 'react-copy-to-clipboard';
import { toast } from 'react-toastify';
import { useState } from 'react';

import useUserPreferences from './useUserPreferences';
import { TimeFormat } from './useUserPreferences';
import AppNav from './AppNav';
import api from './api';
import { isValidUrl } from './utils';
Expand All @@ -34,6 +38,9 @@ export default function TeamPage() {
const rotateTeamApiKey = api.useRotateTeamApiKey();
const saveWebhook = api.useSaveWebhook();
const deleteWebhook = api.useDeleteWebhook();
const setTimeFormat = useUserPreferences().setTimeFormat;
const timeFormat = useUserPreferences().timeFormat;
const handleTimeButtonClick = (val: TimeFormat) => setTimeFormat(val);

const hasAllowedAuthMethods =
team?.allowedAuthMethods != null && team?.allowedAuthMethods.length > 0;
Expand Down Expand Up @@ -442,6 +449,42 @@ export default function TeamPage() {
</Modal.Body>
</Modal>
</div>
<div className="text-muted my-2">
Note: Only affects your own view and does not propagate to other
team members.
</div>
<div>
<h2 className="mt-5">Time Format</h2>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we don't have a global personal setting place, could we maybe for now just put some subtext describing that this setting is a local setting and won't propagate across the entire team? Since this settings page is technically supposed to be the team page.

Something like:

<div className="text-muted my-2">
  Note: Only affects your own view and does not propagate to
  other team members.
</div>

Don't want to block this PR on how we should be treating personal vs team settings :)

Copy link
Contributor Author

@treypisano treypisano Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I'll implement this

<ToggleButtonGroup
type="radio"
value={timeFormat}
onChange={handleTimeButtonClick}
name="buttons"
>
<ToggleButton
id="tbg-btn-1"
value="24h"
variant={
timeFormat === '24h'
? 'outline-success'
: 'outline-secondary'
}
>
24h
</ToggleButton>
<ToggleButton
id="tbg-btn-2"
value="12h"
variant={
timeFormat === '12h'
? 'outline-success'
: 'outline-secondary'
}
>
12h
</ToggleButton>
</ToggleButtonGroup>
</div>
</>
)}
</Container>
Expand Down
29 changes: 25 additions & 4 deletions packages/app/src/useUserPreferences.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useContext, useState } from 'react';

import React, { useContext, useState, useEffect } from 'react';
import { useLocalStorage } from './utils';
export type TimeFormat = '12h' | '24h';

export const UserPreferences = React.createContext({
Expand All @@ -14,16 +14,37 @@ export const UserPreferencesProvider = ({
}: {
children: React.ReactNode;
}) => {
const [storedTF, setTF] = useLocalStorage('timeFormat', '24h');
const setTimeFormat = (timeFormat: TimeFormat) => {
setState(state => ({ ...state, timeFormat }));
setTF(timeFormat);
};
const initState = {
isUTC: false,
timeFormat: '24h' as TimeFormat,
setTimeFormat: (timeFormat: TimeFormat) =>
setState(state => ({ ...state, timeFormat })),
setTimeFormat,
setIsUTC: (isUTC: boolean) => setState(state => ({ ...state, isUTC })),
};

const [state, setState] = useState(initState);

// This only runs once in order to grab and set the initial timeFormat from localStorage
useEffect(() => {
if (typeof window === 'undefined') {
return;
}
try {
let timeFormat = window.localStorage.getItem('timeFormat') as TimeFormat;
if (timeFormat !== null) timeFormat = JSON.parse(timeFormat);

if (timeFormat !== null) {
setState(state => ({ ...state, timeFormat }));
}
} catch (error) {
console.log(error);
}
}, []);

return (
<UserPreferences.Provider value={state}>
{children}
Expand Down
5 changes: 5 additions & 0 deletions packages/app/src/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ export const useDebounce = <T,>(
return debouncedValue;
};

export const TIME_TOKENS = {
'12h': 'MMM d h:mm:ss a',
'24h': 'MMM d HH:mm:ss.SSS',
};

export function useLocalStorage<T>(key: string, initialValue: T) {
// State to store our value
// Pass initial state function to useState so logic is only executed once
Expand Down
Loading