mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-03 08:33:26 +00:00
fix: handle custom time ranges in timezones correctly (#10094)
* feat: handle custom time ranges in timezones correctly * fix: improve timezone UI * fix: prettier issues * fix: show time in user selected timezone, overflow issue (#10096) * fix: show time in user selected timezone, overflow issue, metadata loading * fix: add gap to popover row * fix: use inline conditional render for startTimeMs * fix: remove unused variable in CustomTimePicker * fix: remove unused variable in CustomTimePicker, TraceMetadata * fix: update timezone mock in SpanHoverCard test case
This commit is contained in:
@@ -131,6 +131,45 @@
|
||||
border-top: 1px solid var(--bg-ink-200);
|
||||
padding: 8px 14px;
|
||||
.timezone-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
|
||||
.timezone__name {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--bg-robin-400);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.timezone__separator {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--bg-robin-300);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.timezone__offset {
|
||||
font-size: 11px;
|
||||
line-height: 14px;
|
||||
color: var(--bg-robin-400);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
&__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&,
|
||||
.timezone {
|
||||
font-family: Inter;
|
||||
@@ -138,6 +177,7 @@
|
||||
line-height: 16px;
|
||||
letter-spacing: -0.06px;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--bg-vanilla-400);
|
||||
@@ -156,18 +196,21 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timezone-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 4px;
|
||||
border-radius: 2px;
|
||||
background: rgba(171, 189, 255, 0.04);
|
||||
color: var(--bg-vanilla-100);
|
||||
font-size: 12px;
|
||||
color: var(--bg-vanilla-400);
|
||||
background-color: var(--bg-ink-200);
|
||||
font-size: 9px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
letter-spacing: -0.06px;
|
||||
line-height: 12px;
|
||||
letter-spacing: -0.045px;
|
||||
margin-right: 4px;
|
||||
width: 72px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -183,6 +226,7 @@
|
||||
font-size: 11px;
|
||||
color: var(--bg-vanilla-400);
|
||||
background-color: var(--bg-ink-200);
|
||||
cursor: pointer;
|
||||
|
||||
&.is-live {
|
||||
background-color: transparent;
|
||||
@@ -238,11 +282,10 @@
|
||||
.date-time-popover__footer {
|
||||
border-color: var(--bg-vanilla-400);
|
||||
}
|
||||
|
||||
.timezone-container {
|
||||
color: var(--bg-ink-400);
|
||||
&__clock-icon {
|
||||
stroke: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.timezone {
|
||||
color: var(--bg-ink-100);
|
||||
background: rgb(179 179 179 / 15%);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import './CustomTimePicker.styles.scss';
|
||||
|
||||
import { Input, InputRef, Popover, Tooltip } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
||||
@@ -22,9 +21,7 @@ import {
|
||||
ChangeEvent,
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
@@ -113,22 +110,8 @@ function CustomTimePicker({
|
||||
|
||||
const [activeView, setActiveView] = useState<ViewType>(DEFAULT_VIEW);
|
||||
|
||||
const { timezone, browserTimezone } = useTimezone();
|
||||
const { timezone } = useTimezone();
|
||||
const activeTimezoneOffset = timezone.offset;
|
||||
const isTimezoneOverridden = useMemo(
|
||||
() => timezone.offset !== browserTimezone.offset,
|
||||
[timezone, browserTimezone],
|
||||
);
|
||||
|
||||
const handleViewChange = useCallback(
|
||||
(newView: 'timezone' | 'datetime'): void => {
|
||||
if (activeView !== newView) {
|
||||
setActiveView(newView);
|
||||
}
|
||||
setOpen(true);
|
||||
},
|
||||
[activeView, setOpen],
|
||||
);
|
||||
|
||||
const [isOpenedFromFooter, setIsOpenedFromFooter] = useState(false);
|
||||
|
||||
@@ -371,6 +354,7 @@ function CustomTimePicker({
|
||||
startTime,
|
||||
endTime,
|
||||
DATE_TIME_FORMATS.UK_DATETIME_SECONDS,
|
||||
timezone.value,
|
||||
);
|
||||
|
||||
if (!isValidTimeRange) {
|
||||
@@ -422,8 +406,8 @@ function CustomTimePicker({
|
||||
</div>
|
||||
);
|
||||
|
||||
const handleOpen = (e: React.SyntheticEvent): void => {
|
||||
e.stopPropagation();
|
||||
const handleOpen = (e?: React.SyntheticEvent): void => {
|
||||
e?.stopPropagation?.();
|
||||
|
||||
if (showLiveLogs) {
|
||||
setOpen(true);
|
||||
@@ -436,12 +420,12 @@ function CustomTimePicker({
|
||||
// reset the input status and error message as we reset the time to previous correct value
|
||||
resetErrorStatus();
|
||||
|
||||
const startTime = dayjs(minTime / 1000_000).format(
|
||||
DATE_TIME_FORMATS.UK_DATETIME_SECONDS,
|
||||
);
|
||||
const endTime = dayjs(maxTime / 1000_000).format(
|
||||
DATE_TIME_FORMATS.UK_DATETIME_SECONDS,
|
||||
);
|
||||
const startTime = dayjs(minTime / 1000_000)
|
||||
.tz(timezone.value)
|
||||
.format(DATE_TIME_FORMATS.UK_DATETIME_SECONDS);
|
||||
const endTime = dayjs(maxTime / 1000_000)
|
||||
.tz(timezone.value)
|
||||
.format(DATE_TIME_FORMATS.UK_DATETIME_SECONDS);
|
||||
|
||||
setInputValue(`${startTime} - ${endTime}`);
|
||||
};
|
||||
@@ -468,18 +452,6 @@ function CustomTimePicker({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [location.pathname]);
|
||||
|
||||
const handleTimezoneHintClick = (e: React.MouseEvent): void => {
|
||||
e.stopPropagation();
|
||||
handleViewChange('timezone');
|
||||
setIsOpenedFromFooter(false);
|
||||
logEvent(
|
||||
'DateTimePicker: Timezone picker opened from time range input badge',
|
||||
{
|
||||
page: location.pathname,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handleInputBlur = (): void => {
|
||||
resetErrorStatus();
|
||||
};
|
||||
@@ -498,9 +470,7 @@ function CustomTimePicker({
|
||||
return '';
|
||||
};
|
||||
|
||||
// Focus and select input text when popover opens
|
||||
useEffect(() => {
|
||||
if (open && inputRef.current) {
|
||||
const focusInput = (): void => {
|
||||
// Use setTimeout to wait for React to update the DOM and make input editable
|
||||
setTimeout(() => {
|
||||
const inputElement = inputRef.current?.input;
|
||||
@@ -508,10 +478,20 @@ function CustomTimePicker({
|
||||
inputElement.focus();
|
||||
inputElement.select();
|
||||
}
|
||||
}, 0);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// Focus and select input text when popover opens
|
||||
useEffect(() => {
|
||||
if (open && inputRef.current) {
|
||||
focusInput();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleTimezoneChange = (): void => {
|
||||
focusInput();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="custom-time-picker">
|
||||
<Tooltip title={getTooltipTitle()} placement="top">
|
||||
@@ -532,6 +512,7 @@ function CustomTimePicker({
|
||||
customDateTimeVisible={defaultTo(customDateTimeVisible, false)}
|
||||
onCustomDateHandler={defaultTo(onCustomDateHandler, noop)}
|
||||
onSelectHandler={handleSelect}
|
||||
onTimezoneChange={handleTimezoneChange}
|
||||
onGoLive={defaultTo(onGoLive, noop)}
|
||||
onExitLiveLogs={defaultTo(onExitLiveLogs, noop)}
|
||||
options={items}
|
||||
@@ -583,8 +564,8 @@ function CustomTimePicker({
|
||||
prefix={getInputPrefix()}
|
||||
suffix={
|
||||
<div className="time-input-suffix">
|
||||
{!!isTimezoneOverridden && activeTimezoneOffset && (
|
||||
<div className="timezone-badge" onClick={handleTimezoneHintClick}>
|
||||
{activeTimezoneOffset && (
|
||||
<div className="timezone-badge">
|
||||
<span>{activeTimezoneOffset}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -31,6 +31,7 @@ import { TimeRangeValidationResult } from 'utils/timeUtils';
|
||||
import CalendarContainer from './CalendarContainer';
|
||||
import { CustomTimePickerInputStatus } from './CustomTimePicker';
|
||||
import TimezonePicker from './TimezonePicker';
|
||||
import { Timezone } from './timezoneUtils';
|
||||
|
||||
const TO_MILLISECONDS_FACTOR = 1000_000;
|
||||
|
||||
@@ -52,6 +53,7 @@ interface CustomTimePickerPopoverContentProps {
|
||||
lexicalContext?: LexicalContext,
|
||||
) => void;
|
||||
onSelectHandler: (label: string, value: string) => void;
|
||||
onTimezoneChange: (timezone: Timezone) => void;
|
||||
onGoLive: () => void;
|
||||
selectedTime: string;
|
||||
activeView: 'datetime' | 'timezone';
|
||||
@@ -101,6 +103,7 @@ function CustomTimePickerPopoverContent({
|
||||
setCustomDTPickerVisible,
|
||||
onCustomDateHandler,
|
||||
onSelectHandler,
|
||||
onTimezoneChange,
|
||||
onGoLive,
|
||||
selectedTime,
|
||||
activeView,
|
||||
@@ -208,6 +211,7 @@ function CustomTimePickerPopoverContent({
|
||||
setActiveView={setActiveView}
|
||||
setIsOpen={setIsOpen}
|
||||
isOpenedFromFooter={isOpenedFromFooter}
|
||||
onTimezoneSelect={onTimezoneChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -352,26 +356,30 @@ function CustomTimePickerPopoverContent({
|
||||
|
||||
<div className="date-time-popover__footer">
|
||||
<div className="timezone-container">
|
||||
<div className="timezone-container__left">
|
||||
<Clock
|
||||
color={Color.BG_VANILLA_400}
|
||||
color={Color.BG_ROBIN_400}
|
||||
className="timezone-container__clock-icon"
|
||||
height={12}
|
||||
width={12}
|
||||
/>
|
||||
<span className="timezone__icon">Current timezone</span>
|
||||
<div>⎯</div>
|
||||
<button
|
||||
type="button"
|
||||
className="timezone"
|
||||
|
||||
<span className="timezone__name">{timezone.name}</span>
|
||||
<span className="timezone__separator">⎯</span>
|
||||
<span className="timezone__offset">{activeTimezoneOffset}</span>
|
||||
</div>
|
||||
|
||||
<div className="timezone-container__right">
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
className="periscope-btn text timezone-change-button"
|
||||
onClick={handleTimezoneHintClick}
|
||||
icon={<PenLine size={10} />}
|
||||
>
|
||||
<span>{activeTimezoneOffset}</span>
|
||||
<PenLine
|
||||
color={Color.BG_VANILLA_100}
|
||||
className="timezone__icon"
|
||||
size={10}
|
||||
/>
|
||||
</button>
|
||||
Change Timezone
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -121,12 +121,14 @@ interface TimezonePickerProps {
|
||||
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
|
||||
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
||||
isOpenedFromFooter: boolean;
|
||||
onTimezoneSelect: (timezone: Timezone) => void;
|
||||
}
|
||||
|
||||
function TimezonePicker({
|
||||
setActiveView,
|
||||
setIsOpen,
|
||||
isOpenedFromFooter,
|
||||
onTimezoneSelect,
|
||||
}: TimezonePickerProps): JSX.Element {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const { timezone, updateTimezone } = useTimezone();
|
||||
@@ -153,11 +155,11 @@ function TimezonePicker({
|
||||
}, [isOpenedFromFooter, setActiveView, setIsOpen]);
|
||||
|
||||
const handleTimezoneSelect = useCallback(
|
||||
(timezone: Timezone) => {
|
||||
(timezone: Timezone): void => {
|
||||
setSelectedTimezone(timezone.name);
|
||||
updateTimezone(timezone);
|
||||
onTimezoneSelect(timezone);
|
||||
handleCloseTimezonePicker();
|
||||
setIsOpen(false);
|
||||
logEvent('DateTimePicker: New Timezone Selected', {
|
||||
timezone: {
|
||||
name: timezone.name,
|
||||
@@ -165,7 +167,7 @@ function TimezonePicker({
|
||||
},
|
||||
});
|
||||
},
|
||||
[handleCloseTimezonePicker, setIsOpen, updateTimezone],
|
||||
[handleCloseTimezonePicker, updateTimezone, onTimezoneSelect],
|
||||
);
|
||||
|
||||
// Register keyboard shortcuts
|
||||
@@ -194,7 +196,7 @@ function TimezonePicker({
|
||||
<div className="timezone-picker__list">
|
||||
{getFilteredTimezones(searchTerm).map((timezone) => (
|
||||
<TimezoneItem
|
||||
key={timezone.value}
|
||||
key={`${timezone.value}-${timezone.name}`}
|
||||
timezone={timezone}
|
||||
isSelected={timezone.name === selectedTimezone}
|
||||
onClick={(): void => handleTimezoneSelect(timezone)}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
.span-hover-card {
|
||||
width: 206px;
|
||||
|
||||
.ant-popover-inner {
|
||||
background: linear-gradient(
|
||||
139deg,
|
||||
@@ -60,8 +58,8 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-width: 174px;
|
||||
margin-top: 8px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Popover, Typography } from 'antd';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
|
||||
import dayjs from 'dayjs';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { ReactNode } from 'react';
|
||||
import { Span } from 'types/api/trace/getTraceV2';
|
||||
import { toFixed } from 'utils/toFixed';
|
||||
@@ -29,6 +30,8 @@ function SpanHoverCard({
|
||||
duration,
|
||||
);
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
// Calculate relative start time from trace start
|
||||
const relativeStartTime = span.timestamp - traceMetadata.startTime;
|
||||
const {
|
||||
@@ -37,9 +40,9 @@ function SpanHoverCard({
|
||||
} = convertTimeToRelevantUnit(relativeStartTime);
|
||||
|
||||
// Format absolute start time
|
||||
const startTimeFormatted = dayjs(span.timestamp).format(
|
||||
DATE_TIME_FORMATS.SPAN_POPOVER_DATE,
|
||||
);
|
||||
const startTimeFormatted = dayjs(span.timestamp)
|
||||
.tz(timezone.value)
|
||||
.format(DATE_TIME_FORMATS.DD_MMM_YYYY_HH_MM_SS);
|
||||
|
||||
const getContent = (): JSX.Element => (
|
||||
<div className="span-hover-card">
|
||||
@@ -87,7 +90,7 @@ function SpanHoverCard({
|
||||
</Typography.Text>
|
||||
</div>
|
||||
}
|
||||
mouseEnterDelay={0.5}
|
||||
mouseEnterDelay={0.2}
|
||||
content={getContent()}
|
||||
trigger="hover"
|
||||
rootClassName="span-hover-card"
|
||||
|
||||
@@ -2,21 +2,54 @@ import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { Span } from 'types/api/trace/getTraceV2';
|
||||
|
||||
import SpanHoverCard from '../SpanHoverCard';
|
||||
import { TimezoneContextType } from 'providers/Timezone';
|
||||
|
||||
// Mock dayjs completely for testing
|
||||
jest.mock('dayjs', () => {
|
||||
const mockDayjs = jest.fn(() => ({
|
||||
format: jest.fn((formatString: string) => {
|
||||
if (formatString === 'D/M/YY - HH:mm:ss') {
|
||||
return '15/3/24 - 14:23:45';
|
||||
}
|
||||
return 'mock-date';
|
||||
// Mock timezone provider so SpanHoverCard can use useTimezone without a real context
|
||||
jest.mock('providers/Timezone', () => ({
|
||||
__esModule: true,
|
||||
useTimezone: (): TimezoneContextType => ({
|
||||
timezone: {
|
||||
name: 'Coordinated Universal Time — UTC, GMT',
|
||||
value: 'UTC',
|
||||
offset: 'UTC',
|
||||
searchIndex: 'UTC',
|
||||
},
|
||||
browserTimezone: {
|
||||
name: 'Coordinated Universal Time — UTC, GMT',
|
||||
value: 'UTC',
|
||||
offset: 'UTC',
|
||||
searchIndex: 'UTC',
|
||||
},
|
||||
updateTimezone: jest.fn(),
|
||||
formatTimezoneAdjustedTimestamp: jest.fn(() => 'mock-date'),
|
||||
isAdaptationEnabled: true,
|
||||
setIsAdaptationEnabled: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
}));
|
||||
|
||||
// Mock dayjs for testing, including timezone helpers used in timezoneUtils
|
||||
jest.mock('dayjs', () => {
|
||||
const mockDayjsInstance: any = {};
|
||||
|
||||
mockDayjsInstance.format = jest.fn((formatString: string) =>
|
||||
// Match the DD_MMM_YYYY_HH_MM_SS format: 'DD MMM YYYY, HH:mm:ss'
|
||||
formatString === 'DD MMM YYYY, HH:mm:ss'
|
||||
? '15 Mar 2024, 14:23:45'
|
||||
: 'mock-date',
|
||||
);
|
||||
|
||||
// Support chaining: dayjs().tz(timezone).format(...) and dayjs().tz(timezone).utcOffset()
|
||||
mockDayjsInstance.tz = jest.fn(() => mockDayjsInstance);
|
||||
mockDayjsInstance.utcOffset = jest.fn(() => 0);
|
||||
|
||||
const mockDayjs = jest.fn(() => mockDayjsInstance);
|
||||
|
||||
Object.assign(mockDayjs, {
|
||||
extend: jest.fn(),
|
||||
// Support dayjs.tz.guess()
|
||||
tz: { guess: jest.fn(() => 'UTC') },
|
||||
});
|
||||
|
||||
return mockDayjs;
|
||||
});
|
||||
|
||||
@@ -84,7 +117,7 @@ describe('SpanHoverCard', () => {
|
||||
expect(screen.getByText('Hover me')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows popover after 0.5 second delay on hover', async () => {
|
||||
it('shows popover after 0.2 second delay on hover', async () => {
|
||||
render(
|
||||
<SpanHoverCard span={mockSpan} traceMetadata={mockTraceMetadata}>
|
||||
<div data-testid={HOVER_ELEMENT_ID}>Hover for details</div>
|
||||
@@ -101,7 +134,7 @@ describe('SpanHoverCard', () => {
|
||||
|
||||
// Advance time by 0.5 seconds
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(500);
|
||||
jest.advanceTimersByTime(200);
|
||||
});
|
||||
|
||||
// Now popover should appear
|
||||
@@ -117,10 +150,10 @@ describe('SpanHoverCard', () => {
|
||||
|
||||
const hoverElement = screen.getByTestId(HOVER_ELEMENT_ID);
|
||||
|
||||
// Quick hover and unhover
|
||||
// Quick hover and unhover (less than the 0.2s delay)
|
||||
fireEvent.mouseEnter(hoverElement);
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(200); // Only 0.2 seconds
|
||||
jest.advanceTimersByTime(100); // Only 0.1 seconds
|
||||
});
|
||||
fireEvent.mouseLeave(hoverElement);
|
||||
|
||||
@@ -163,7 +196,7 @@ describe('SpanHoverCard', () => {
|
||||
expect(screen.getByText('Start time:')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays new date format with seconds', async () => {
|
||||
it('displays date in DD MMM YYYY, HH:mm:ss format with seconds', async () => {
|
||||
render(
|
||||
<SpanHoverCard span={mockSpan} traceMetadata={mockTraceMetadata}>
|
||||
<div data-testid={HOVER_ELEMENT_ID}>Date format test</div>
|
||||
@@ -178,8 +211,8 @@ describe('SpanHoverCard', () => {
|
||||
jest.advanceTimersByTime(500);
|
||||
});
|
||||
|
||||
// Verify the new date format is displayed
|
||||
expect(screen.getByText('15/3/24 - 14:23:45')).toBeInTheDocument();
|
||||
// Verify the DD MMM YYYY, HH:mm:ss format is displayed
|
||||
expect(screen.getByText('15 Mar 2024, 14:23:45')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays relative time information', async () => {
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
.trace-id {
|
||||
color: #fff;
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 128.571% */
|
||||
@@ -59,7 +59,7 @@
|
||||
background: var(--bg-slate-400);
|
||||
color: var(--Vanilla-400, #c0c1c3);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 128.571% */
|
||||
@@ -83,7 +83,7 @@
|
||||
.text {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
@@ -110,7 +110,7 @@
|
||||
.text {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
@@ -127,7 +127,7 @@
|
||||
.text {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
@@ -156,7 +156,7 @@
|
||||
color: var(--bg-vanilla-400);
|
||||
text-align: center;
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 128.571% */
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import './TraceMetadata.styles.scss';
|
||||
|
||||
import { Button, Tooltip, Typography } from 'antd';
|
||||
import { Button, Skeleton, Tooltip, Typography } from 'antd';
|
||||
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import ROUTES from 'constants/routes';
|
||||
import dayjs from 'dayjs';
|
||||
import history from 'lib/history';
|
||||
import {
|
||||
ArrowLeft,
|
||||
@@ -11,7 +13,8 @@ import {
|
||||
DraftingCompass,
|
||||
Timer,
|
||||
} from 'lucide-react';
|
||||
import { formatEpochTimestamp } from 'utils/timeUtils';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export interface ITraceMetadataProps {
|
||||
traceID: string;
|
||||
@@ -22,6 +25,7 @@ export interface ITraceMetadataProps {
|
||||
totalSpans: number;
|
||||
totalErrorSpans: number;
|
||||
notFound: boolean;
|
||||
isDataLoading: boolean;
|
||||
}
|
||||
|
||||
function TraceMetadata(props: ITraceMetadataProps): JSX.Element {
|
||||
@@ -34,8 +38,19 @@ function TraceMetadata(props: ITraceMetadataProps): JSX.Element {
|
||||
totalErrorSpans,
|
||||
totalSpans,
|
||||
notFound,
|
||||
isDataLoading,
|
||||
} = props;
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
const startTimeInMs = useMemo(
|
||||
() =>
|
||||
dayjs(startTime * 1e3)
|
||||
.tz(timezone.value)
|
||||
.format(DATE_TIME_FORMATS.DD_MMM_YYYY_HH_MM_SS),
|
||||
[startTime, timezone.value],
|
||||
);
|
||||
|
||||
const handlePreviousBtnClick = (): void => {
|
||||
if (window.history.length > 1) {
|
||||
history.goBack();
|
||||
@@ -57,7 +72,18 @@ function TraceMetadata(props: ITraceMetadataProps): JSX.Element {
|
||||
</div>
|
||||
<Typography.Text className="trace-id-value">{traceID}</Typography.Text>
|
||||
</div>
|
||||
{!notFound && (
|
||||
|
||||
{isDataLoading && (
|
||||
<div className="second-row">
|
||||
<div className="service-entry-info">
|
||||
<BetweenHorizonalStart size={14} />
|
||||
<Skeleton.Input active className="skeleton-input" size="small" />
|
||||
<Skeleton.Input active className="skeleton-input" size="small" />
|
||||
<Skeleton.Input active className="skeleton-input" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!isDataLoading && !notFound && (
|
||||
<div className="second-row">
|
||||
<div className="service-entry-info">
|
||||
<BetweenHorizonalStart size={14} />
|
||||
@@ -79,8 +105,9 @@ function TraceMetadata(props: ITraceMetadataProps): JSX.Element {
|
||||
<Tooltip title="Start timestamp">
|
||||
<CalendarClock size={14} />
|
||||
</Tooltip>
|
||||
|
||||
<Typography.Text className="text">
|
||||
{formatEpochTimestamp(startTime * 1000)}
|
||||
{startTimeInMs || 'N/A'}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -135,6 +135,7 @@ function TraceDetailsV2(): JSX.Element {
|
||||
<ResizablePanel minSize={20} maxSize={80} className="trace-left-content">
|
||||
<TraceMetadata
|
||||
traceID={traceId}
|
||||
isDataLoading={isFetchingTraceData}
|
||||
duration={
|
||||
(traceData?.payload?.endTimestampMillis || 0) -
|
||||
(traceData?.payload?.startTimestampMillis || 0)
|
||||
|
||||
@@ -198,6 +198,14 @@
|
||||
border-color: var(--bg-vanilla-300);
|
||||
background: var(--bg-vanilla-100);
|
||||
color: var(--bg-ink-200);
|
||||
|
||||
&.text {
|
||||
color: var(--bg-ink-200) !important;
|
||||
|
||||
&:hover {
|
||||
color: var(--bg-ink-300) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.periscope-input-with-label {
|
||||
|
||||
@@ -18,7 +18,7 @@ import React, {
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
interface TimezoneContextType {
|
||||
export interface TimezoneContextType {
|
||||
timezone: Timezone;
|
||||
browserTimezone: Timezone;
|
||||
updateTimezone: (timezone: Timezone) => void;
|
||||
|
||||
@@ -2,10 +2,13 @@ import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import dayjs from 'dayjs';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
import duration from 'dayjs/plugin/duration';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
dayjs.extend(duration);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
export function toUTCEpoch(time: number): number {
|
||||
const x = new Date();
|
||||
@@ -213,10 +216,11 @@ export const validateTimeRange = (
|
||||
startTime: string,
|
||||
endTime: string,
|
||||
format: string,
|
||||
timezone: string,
|
||||
): TimeRangeValidationResult => {
|
||||
const start = dayjs(startTime, format, true);
|
||||
const end = dayjs(endTime, format, true);
|
||||
const now = dayjs();
|
||||
const start = dayjs.tz(startTime, format, timezone);
|
||||
const end = dayjs.tz(endTime, format, timezone);
|
||||
const now = dayjs().tz(timezone);
|
||||
const startTimeMs = start.valueOf();
|
||||
const endTimeMs = end.valueOf();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user