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 26 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
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ function CustomTimePicker({
setOpen(newOpen);
if (!newOpen) {
setCustomDTPickerVisible?.(false);
setActiveView('datetime');
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DatePicker } from 'antd';
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
import { LexicalContext } from 'container/TopNav/DateTimeSelectionV2/config';
import dayjs, { Dayjs } from 'dayjs';
import { useTimezone } from 'providers/Timezone';
import { Dispatch, SetStateAction } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
Expand Down Expand Up @@ -49,6 +50,8 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
}
onCustomDateHandler(date_time, LexicalContext.CUSTOM_DATE_PICKER);
};

const { timezone } = useTimezone();
return (
<div className="custom-date-picker">
<RangePicker
Expand All @@ -58,7 +61,10 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
onOk={onModalOkHandler}
// eslint-disable-next-line react/jsx-props-no-spreading
{...(selectedTime === 'custom' && {
defaultValue: [dayjs(minTime / 1000000), dayjs(maxTime / 1000000)],
defaultValue: [
dayjs(minTime / 1000000).tz(timezone.value),
dayjs(maxTime / 1000000).tz(timezone.value),
],
})}
/>
</div>
Expand Down
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
16 changes: 12 additions & 4 deletions frontend/src/components/Logs/ListLogView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import LogDetail from 'components/LogDetail';
import { VIEW_TYPES } from 'components/LogDetail/constants';
import { unescapeString } from 'container/LogDetailedView/utils';
import { FontSize } from 'container/OptionsMenu/types';
import dayjs from 'dayjs';
import dompurify from 'dompurify';
import { useActiveLog } from 'hooks/logs/useActiveLog';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
import { useIsDarkMode } from 'hooks/useDarkMode';
// utils
import { FlatLogData } from 'lib/logs/flatLogData';
import { useTimezone } from 'providers/Timezone';
import { useCallback, useMemo, useState } from 'react';
// interfaces
import { IField } from 'types/api/logs/fields';
Expand Down Expand Up @@ -174,12 +174,20 @@ function ListLogView({
[selectedFields],
);

const { formatTimezoneAdjustedTimestamp } = useTimezone();

const timestampValue = useMemo(
() =>
typeof flattenLogData.timestamp === 'string'
? dayjs(flattenLogData.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS')
: dayjs(flattenLogData.timestamp / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS'),
[flattenLogData.timestamp],
? formatTimezoneAdjustedTimestamp(
flattenLogData.timestamp,
'YYYY-MM-DD HH:mm:ss.SSS',
)
: formatTimezoneAdjustedTimestamp(
flattenLogData.timestamp / 1e6,
'YYYY-MM-DD HH:mm:ss.SSS',
),
[flattenLogData.timestamp, formatTimezoneAdjustedTimestamp],
);

const logType = getLogIndicatorType(logData);
Expand Down
24 changes: 16 additions & 8 deletions frontend/src/components/Logs/RawLogView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import LogDetail from 'components/LogDetail';
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
import { unescapeString } from 'container/LogDetailedView/utils';
import LogsExplorerContext from 'container/LogsExplorerContext';
import dayjs from 'dayjs';
import dompurify from 'dompurify';
import { useActiveLog } from 'hooks/logs/useActiveLog';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
// hooks
import { useIsDarkMode } from 'hooks/useDarkMode';
import { FlatLogData } from 'lib/logs/flatLogData';
import { isEmpty, isNumber, isUndefined } from 'lodash-es';
import { useTimezone } from 'providers/Timezone';
import {
KeyboardEvent,
MouseEvent,
Expand Down Expand Up @@ -89,16 +89,24 @@ function RawLogView({
attributesText += ' | ';
}

const { formatTimezoneAdjustedTimestamp } = useTimezone();

const text = useMemo(() => {
const date =
typeof data.timestamp === 'string'
? dayjs(data.timestamp)
: dayjs(data.timestamp / 1e6);

return `${date.format('YYYY-MM-DD HH:mm:ss.SSS')} | ${attributesText} ${
data.body
}`;
}, [data.timestamp, data.body, attributesText]);
? formatTimezoneAdjustedTimestamp(data.timestamp, 'YYYY-MM-DD HH:mm:ss.SSS')
: formatTimezoneAdjustedTimestamp(
data.timestamp / 1e6,
'YYYY-MM-DD HH:mm:ss.SSS',
);

return `${date} | ${attributesText} ${data.body}`;
}, [
data.timestamp,
data.body,
attributesText,
formatTimezoneAdjustedTimestamp,
]);

const handleClickExpand = useCallback(() => {
if (activeContextLog || isReadOnly) return;
Expand Down
21 changes: 17 additions & 4 deletions frontend/src/components/Logs/TableView/useTableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { Typography } from 'antd';
import { ColumnsType } from 'antd/es/table';
import cx from 'classnames';
import { unescapeString } from 'container/LogDetailedView/utils';
import dayjs from 'dayjs';
import dompurify from 'dompurify';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { FlatLogData } from 'lib/logs/flatLogData';
import { useTimezone } from 'providers/Timezone';
import { useMemo } from 'react';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';

Expand Down Expand Up @@ -44,6 +44,8 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
logs,
]);

const { formatTimezoneAdjustedTimestamp } = useTimezone();

const columns: ColumnsType<Record<string, unknown>> = useMemo(() => {
const fieldColumns: ColumnsType<Record<string, unknown>> = fields
.filter((e) => e.name !== 'id')
Expand Down Expand Up @@ -81,8 +83,11 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
render: (field, item): ColumnTypeRender<Record<string, unknown>> => {
const date =
typeof field === 'string'
? dayjs(field).format('YYYY-MM-DD HH:mm:ss.SSS')
: dayjs(field / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS');
? formatTimezoneAdjustedTimestamp(field, 'YYYY-MM-DD HH:mm:ss.SSS')
: formatTimezoneAdjustedTimestamp(
field / 1e6,
'YYYY-MM-DD HH:mm:ss.SSS',
);
return {
children: (
<div className="table-timestamp">
Expand Down Expand Up @@ -125,7 +130,15 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
},
...(appendTo === 'end' ? fieldColumns : []),
];
}, [fields, isListViewPanel, appendTo, isDarkMode, linesPerRow, fontSize]);
}, [
fields,
isListViewPanel,
appendTo,
isDarkMode,
linesPerRow,
fontSize,
formatTimezoneAdjustedTimestamp,
]);

return { columns, dataSource: flattenLogData };
};
10 changes: 6 additions & 4 deletions frontend/src/components/ResizeTable/TableComponent/Time.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Typography } from 'antd';
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm';
import getFormattedDate from 'lib/getFormatedDate';
import { useTimezone } from 'providers/Timezone';

function Time({ CreatedOrUpdateTime }: DateProps): JSX.Element {
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const time = new Date(CreatedOrUpdateTime);
const date = getFormattedDate(time);
const timeString = `${date} ${convertDateToAmAndPm(time)}`;
const timeString = formatTimezoneAdjustedTimestamp(
time,
'MM/DD/YYYY hh:mm:ss A (UTC Z)',
);
return <Typography>{timeString}</Typography>;
}

Expand Down
4 changes: 4 additions & 0 deletions frontend/src/container/AlertHistory/Timeline/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useGetAlertRuleDetailsTimelineTable,
useTimelineTable,
} from 'pages/AlertDetails/hooks';
import { useTimezone } from 'providers/Timezone';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
Expand Down Expand Up @@ -39,6 +40,8 @@ function TimelineTable(): JSX.Element {

const { t } = useTranslation('common');

const { formatTimezoneAdjustedTimestamp } = useTimezone();

if (isError || !isValidRuleId || !ruleId) {
return <div>{t('something_went_wrong')}</div>;
}
Expand All @@ -51,6 +54,7 @@ function TimelineTable(): JSX.Element {
filters,
labels: labels ?? {},
setFilters,
formatTimezoneAdjustedTimestamp,
})}
dataSource={timelineData}
pagination={paginationConfig}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ClientSideQBSearch, {
import { ConditionalAlertPopover } from 'container/AlertHistory/AlertPopover/AlertPopover';
import { transformKeyValuesToAttributeValuesMap } from 'container/QueryBuilder/filters/utils';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
import { Search } from 'lucide-react';
import AlertLabels, {
AlertLabelsProps,
Expand All @@ -16,7 +17,6 @@ import AlertState from 'pages/AlertDetails/AlertHeader/AlertState/AlertState';
import { useMemo } from 'react';
import { AlertRuleTimelineTableResponse } from 'types/api/alerts/def';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { formatEpochTimestamp } from 'utils/timeUtils';

const transformLabelsToQbKeys = (
labels: AlertRuleTimelineTableResponse['labels'],
Expand Down Expand Up @@ -74,10 +74,15 @@ export const timelineTableColumns = ({
filters,
labels,
setFilters,
formatTimezoneAdjustedTimestamp,
}: {
filters: TagFilter;
labels: AlertLabelsProps['labels'];
setFilters: (filters: TagFilter) => void;
formatTimezoneAdjustedTimestamp: (
input: TimestampInput,
format?: string,
) => string;
}): ColumnsType<AlertRuleTimelineTableResponse> => [
{
title: 'STATE',
Expand Down Expand Up @@ -106,7 +111,9 @@ export const timelineTableColumns = ({
dataIndex: 'unixMilli',
width: 200,
render: (value): JSX.Element => (
<div className="alert-rule__created-at">{formatEpochTimestamp(value)}</div>
<div className="alert-rule__created-at">
{formatTimezoneAdjustedTimestamp(value, 'MMM D, YYYY ⎯ HH:mm:ss')}
</div>
),
},
{
Expand Down
Loading
Loading