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

feat: add timezone support to logs explorer chart (built with Chartjs) #6566

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b66328e
feat: display timezone adjusted time range in time picker
ahmadshaheer Nov 25, 2024
fb78018
fix: make x axis and tooltip time format consistent in uPlot graphs
ahmadshaheer Nov 25, 2024
ac4613c
fix: open the timepicker on clicking the input after closing the time…
ahmadshaheer Nov 25, 2024
4a6c146
feat: custom hook for formatting timezone
ahmadshaheer Nov 26, 2024
eb07ab8
feat: add timezone support to traces explorer timestamp column
ahmadshaheer Nov 26, 2024
42def1e
feat: add timezone support to saved views
ahmadshaheer Nov 26, 2024
d3d8dd6
chore: improve timezone formatter custom hook
ahmadshaheer Nov 26, 2024
1e8253f
feat: add support for timezone adjusted timestamp in raw log view (lo…
ahmadshaheer Nov 26, 2024
21c0197
feat: add support for timezone adjusted timestamp in log table view (…
ahmadshaheer Nov 26, 2024
29e5575
feat: add support for timezone adjusted timestamp in log default view…
ahmadshaheer Nov 26, 2024
b35a004
feat: add support for timezone adjusted timestamp in log details side…
ahmadshaheer Nov 26, 2024
1674b55
feat: add support for timezone adjusted timestamp in pipeline pages
ahmadshaheer Nov 26, 2024
e7b2a5a
feat: add support for timezone in dashboard list
ahmadshaheer Dec 1, 2024
86626df
feat: add support for timezone adjusted created/updated at in alert r…
ahmadshaheer Dec 1, 2024
c016dc3
feat: add support for timezone adjusted created at in timeline table …
ahmadshaheer Dec 1, 2024
e99fe8d
feat: add support for timezone adjusted Firing Since in triggered ale…
ahmadshaheer Dec 1, 2024
ef9ab7c
feat: add support for timezone adjusted Timestamp for List Panel of d…
ahmadshaheer Dec 1, 2024
88e2d39
feat: add support for timezone adjusted First/Last Seen in exceptions…
ahmadshaheer Dec 1, 2024
c20dc77
feat: add support for timezone adjusted date in exception details page
ahmadshaheer Dec 1, 2024
614823a
feat: add support for timezone adjusted Valid From/To in History tab …
ahmadshaheer Dec 1, 2024
a7da3f3
chore: rename formatTimestamp -> formatTimezoneAdjustedTimestamp
ahmadshaheer Dec 1, 2024
fc88e02
feat: add timezone support to logs explorer chart (built with Chartjs)
ahmadshaheer Dec 1, 2024
a5df35f
chore: remove unnecessary chartjs-adapter-date-fns import
ahmadshaheer Dec 2, 2024
92aca21
chore: improve cache key
ahmadshaheer Dec 2, 2024
44a3f1b
chore: make clearCacheEntries DRYer
ahmadshaheer Dec 2, 2024
2b4b0ed
chore: add docstring to formatTimezoneAdjustedTimestamp
ahmadshaheer Dec 2, 2024
9b87d6d
Merge branch 'feat/apply-timezone-to-tabular-data-throughout-the-app'…
ahmadshaheer Dec 3, 2024
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
34 changes: 34 additions & 0 deletions frontend/src/components/Graph/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
_adapters,
BarController,
BarElement,
CategoryScale,
Expand All @@ -18,8 +19,10 @@ import {
} from 'chart.js';
import annotationPlugin from 'chartjs-plugin-annotation';
import { generateGridTitle } from 'container/GridPanelSwitch/utils';
import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode';
import isEqual from 'lodash-es/isEqual';
import { useTimezone } from 'providers/Timezone';
import {
forwardRef,
memo,
Expand Down Expand Up @@ -62,6 +65,17 @@ Chart.register(

Tooltip.positioners.custom = TooltipPositionHandler;

// Map of Chart.js time formats to dayjs format strings
const formatMap = {
'HH:mm:ss': 'HH:mm:ss',
'HH:mm': 'HH:mm',
'MM/DD HH:mm': 'MM/DD HH:mm',
'MM/dd HH:mm': 'MM/DD HH:mm',
'MM/DD': 'MM/DD',
'YY-MM': 'YY-MM',
YY: 'YY',
};

const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
(
{
Expand All @@ -80,11 +94,13 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
dragSelectColor,
},
ref,
// eslint-disable-next-line sonarjs/cognitive-complexity
): JSX.Element => {
const nearestDatasetIndex = useRef<null | number>(null);
const chartRef = useRef<HTMLCanvasElement>(null);
const isDarkMode = useIsDarkMode();
const gridTitle = useMemo(() => generateGridTitle(title), [title]);
const { timezone } = useTimezone();

const currentTheme = isDarkMode ? 'dark' : 'light';
const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data
Expand Down Expand Up @@ -112,6 +128,22 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
return 'rgba(231,233,237,0.8)';
}, [currentTheme]);

// Override Chart.js date adapter to use dayjs with timezone support
useEffect(() => {
_adapters._date.override({
format(time: number | Date, fmt: string) {
const dayjsTime = dayjs(time).tz(timezone?.value);
const format = formatMap[fmt as keyof typeof formatMap];
if (!format) {
console.warn(`Missing datetime format for ${fmt}`);
return dayjsTime.format('YYYY-MM-DD HH:mm:ss'); // fallback format
}

return dayjsTime.format(format);
},
});
}, [timezone]);

const buildChart = useCallback(() => {
if (lineChartRef.current !== undefined) {
lineChartRef.current.destroy();
Expand All @@ -132,6 +164,7 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
isStacked,
onClickHandler,
data,
timezone,
);

const chartHasData = hasData(data);
Expand Down Expand Up @@ -166,6 +199,7 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
isStacked,
onClickHandler,
data,
timezone,
name,
type,
]);
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/Graph/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Chart, ChartConfiguration, ChartData, Color } from 'chart.js';
import * as chartjsAdapter from 'chartjs-adapter-date-fns';
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
import dayjs from 'dayjs';
import { MutableRefObject } from 'react';

Expand Down Expand Up @@ -50,6 +51,7 @@ export const getGraphOptions = (
isStacked: boolean | undefined,
onClickHandler: GraphOnClickHandler | undefined,
data: ChartData,
timezone: Timezone,
// eslint-disable-next-line sonarjs/cognitive-complexity
): CustomChartOptions => ({
animation: {
Expand Down Expand Up @@ -97,7 +99,7 @@ export const getGraphOptions = (
callbacks: {
title(context): string | string[] {
const date = dayjs(context[0].parsed.x);
return date.format('MMM DD, YYYY, HH:mm:ss');
return date.tz(timezone?.value).format('MMM DD, YYYY, HH:mm:ss');
},
label(context): string | string[] {
let label = context.dataset.label || '';
Expand Down
37 changes: 21 additions & 16 deletions frontend/src/hooks/useTimezoneFormatter/useTimezoneFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,39 @@ function useTimezoneFormatter({
cache.clear();
}, [cache, userTimezone]);
ahmadshaheer marked this conversation as resolved.
Show resolved Hide resolved

const clearExpiredEntries = useCallback(() => {
const clearCacheEntries = useCallback(() => {
if (cache.size <= CACHE_SIZE_LIMIT) return;

// Sort entries by timestamp (oldest first)
const sortedEntries = Array.from(cache.entries()).sort(
(a, b) => a[1].timestamp - b[1].timestamp,
);

// Calculate how many entries to remove
const entriesToRemove = Math.floor(cache.size * CACHE_CLEANUP_PERCENTAGE);
// Calculate how many entries to remove (50% or overflow, whichever is larger)
const entriesToRemove = Math.max(
Math.floor(cache.size * CACHE_CLEANUP_PERCENTAGE),
cache.size - CACHE_SIZE_LIMIT,
);

// Remove oldest entries
sortedEntries.slice(0, entriesToRemove).forEach(([key]) => cache.delete(key));
}, [cache]);

/**
* Formats a timestamp with the user's timezone and caches the result
* @param {TimestampInput} input - The timestamp to format (string, number, or Date)
* @param {string} [format='YYYY-MM-DD HH:mm:ss'] - The desired output format
* @returns {string} The formatted timestamp string in the user's timezone
* @example
* // Input: UTC timestamp
* // User timezone: 'UTC - 4'
* // Returns: "2024-03-14 15:30:00"
* formatTimezoneAdjustedTimestamp('2024-03-14T19:30:00Z')
*/
const formatTimezoneAdjustedTimestamp = useCallback(
(input: TimestampInput, format = 'YYYY-MM-DD HH:mm:ss'): string => {
const cacheKey = `${input}_${format}_${userTimezone?.value}`;
const timestamp = dayjs(input).valueOf();
const cacheKey = `${timestamp}_${userTimezone?.value}`;

// Check cache first
const cachedValue = cache.get(cacheKey);
Expand All @@ -74,22 +89,12 @@ function useTimezoneFormatter({

// Clear expired entries and enforce size limit
if (cache.size > CACHE_SIZE_LIMIT) {
ahmadshaheer marked this conversation as resolved.
Show resolved Hide resolved
clearExpiredEntries();

// If still over limit, remove oldest entries
const entriesToDelete = cache.size - CACHE_SIZE_LIMIT;
if (entriesToDelete > 0) {
const entries = Array.from(cache.entries());
entries
.sort((a, b) => a[1].timestamp - b[1].timestamp)
.slice(0, entriesToDelete)
.forEach(([key]) => cache.delete(key));
}
clearCacheEntries();
}
ahmadshaheer marked this conversation as resolved.
Show resolved Hide resolved

return formattedValue;
},
[cache, clearExpiredEntries, userTimezone],
[cache, clearCacheEntries, userTimezone],
);

return { formatTimezoneAdjustedTimestamp };
Expand Down
Loading