feat: date picker in domain details drawer

This commit is contained in:
sawhil 2025-04-25 03:28:22 +05:30 committed by Sahil Khan
parent 7dad5dcd17
commit d5e2841083
9 changed files with 149 additions and 80 deletions

View File

@ -21,13 +21,10 @@ import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useQueries } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import ErrorState from './components/ErrorState';
import ExpandedRow from './components/ExpandedRow';
@ -39,12 +36,17 @@ function AllEndPoints({
setSelectedView,
groupBy,
setGroupBy,
timeRange,
}: {
domainName: string;
setSelectedEndPointName: (name: string) => void;
setSelectedView: (tab: VIEWS) => void;
groupBy: IBuilderQuery['groupBy'];
setGroupBy: (groupBy: IBuilderQuery['groupBy']) => void;
timeRange: {
startTime: number;
endTime: number;
};
}): JSX.Element {
const {
data: groupByFiltersData,
@ -99,21 +101,15 @@ function AllEndPoints({
}
}, [groupByFiltersData]);
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const { startTime: minTime, endTime: maxTime } = timeRange;
const queryPayloads = useMemo(
() =>
getEndPointsQueryPayload(
groupBy,
domainName,
Math.floor(minTime / 1e9),
Math.floor(maxTime / 1e9),
),
() => getEndPointsQueryPayload(groupBy, domainName, minTime, maxTime),
[groupBy, domainName, minTime, maxTime],
);
console.log('uncaught modalTimeRange', timeRange);
// Since only one query here
const endPointsDataQueries = useQueries(
queryPayloads.map((payload) => ({

View File

@ -12,6 +12,12 @@
background: var(--bg-ink-300);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}
.domain-details-drawer-header-right-container {
display: flex;
align-items: center;
gap: 12px;
}
}
.domain-detail-drawer {

View File

@ -3,16 +3,27 @@ import './DomainDetails.styles.scss';
import { Color, Spacing } from '@signozhq/design-tokens';
import { Button, Divider, Drawer, Radio, Typography } from 'antd';
import { RadioChangeEvent } from 'antd/lib';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import {
CustomTimeType,
Time,
} from 'container/TopNav/DateTimeSelectionV2/config';
import { useIsDarkMode } from 'hooks/useDarkMode';
import GetMinMax from 'lib/getMinMax';
import { ArrowDown, ArrowUp, X } from 'lucide-react';
import { useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime';
import AllEndPoints from './AllEndPoints';
import DomainMetrics from './components/DomainMetrics';
import { VIEW_TYPES, VIEWS } from './constants';
import EndPointDetailsWrapper from './EndPointDetailsWrapper';
const TimeRangeOffset = 1000000000;
function DomainDetails({
domainData,
handleClose,
@ -39,6 +50,49 @@ function DomainDetails({
setSelectedView(e.target.value);
};
const { maxTime, minTime, selectedTime } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const startMs = useMemo(() => Math.floor(Number(minTime) / TimeRangeOffset), [
minTime,
]);
const endMs = useMemo(() => Math.floor(Number(maxTime) / TimeRangeOffset), [
maxTime,
]);
const [selectedInterval, setSelectedInterval] = useState<Time>(
selectedTime as Time,
);
const [modalTimeRange, setModalTimeRange] = useState(() => ({
startTime: startMs,
endTime: endMs,
}));
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
setModalTimeRange({
startTime: Math.floor(dateTimeRange[0] / 1000),
endTime: Math.floor(dateTimeRange[1] / 1000),
});
} else {
const { maxTime, minTime } = GetMinMax(interval);
setModalTimeRange({
startTime: Math.floor(minTime / TimeRangeOffset),
endTime: Math.floor(maxTime / TimeRangeOffset),
});
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
return (
<Drawer
width="60%"
@ -50,6 +104,15 @@ function DomainDetails({
{domainData.domainName}
</Typography.Text>
</div>
<div className="domain-details-drawer-header-right-container">
<DateTimeSelectionV2
showAutoRefresh={false}
showRefreshText={false}
onTimeChange={handleTimeChange}
defaultRelativeTime="5m"
isModalTimeSelection
modalSelectedInterval={selectedInterval}
/>
<Button.Group className="domain-details-drawer-header-ctas">
<Button
className="domain-navigate-cta"
@ -77,6 +140,7 @@ function DomainDetails({
/>
</Button.Group>
</div>
</div>
}
placement="right"
onClose={handleClose}
@ -126,6 +190,7 @@ function DomainDetails({
setSelectedView={setSelectedView}
groupBy={endPointsGroupBy}
setGroupBy={setEndPointsGroupBy}
timeRange={modalTimeRange}
/>
)}
@ -135,6 +200,7 @@ function DomainDetails({
endPointName={selectedEndPointName}
setSelectedEndPointName={setSelectedEndPointName}
domainListFilters={domainListFilters}
timeRange={modalTimeRange}
/>
)}
</>

View File

@ -11,13 +11,10 @@ import QueryBuilderSearchV2 from 'container/QueryBuilder/filters/QueryBuilderSea
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { useMemo, useState } from 'react';
import { useQueries } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import DependentServices from './components/DependentServices';
import EndPointMetrics from './components/EndPointMetrics';
@ -31,15 +28,18 @@ function EndPointDetails({
endPointName,
setSelectedEndPointName,
domainListFilters,
timeRange,
}: {
domainName: string;
endPointName: string;
setSelectedEndPointName: (value: string) => void;
domainListFilters: IBuilderQuery['filters'];
timeRange: {
startTime: number;
endTime: number;
};
}): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const { startTime: minTime, endTime: maxTime } = timeRange;
const currentQuery = initialQueriesMap[DataSource.TRACES];
@ -82,8 +82,8 @@ function EndPointDetails({
getEndPointDetailsQueryPayload(
domainName,
endPointName,
Math.floor(minTime / 1e9),
Math.floor(maxTime / 1e9),
minTime,
maxTime,
filters,
),
[domainName, endPointName, filters, minTime, maxTime],
@ -188,10 +188,11 @@ function EndPointDetails({
endPointName={endPointName}
domainListFilters={domainListFilters}
filters={filters}
timeRange={timeRange}
/>
<StatusCodeTable endPointStatusCodeDataQuery={endPointStatusCodeDataQuery} />
<MetricOverTimeGraph widget={rateOverTimeWidget} />
<MetricOverTimeGraph widget={latencyOverTimeWidget} />
<MetricOverTimeGraph widget={rateOverTimeWidget} timeRange={timeRange} />
<MetricOverTimeGraph widget={latencyOverTimeWidget} timeRange={timeRange} />
</div>
);
}

View File

@ -4,12 +4,9 @@ import { getEndPointZeroStateQueryPayload } from 'container/ApiMonitoring/utils'
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { useMemo } from 'react';
import { useQueries } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime';
import EndPointDetailsZeroState from './components/EndPointDetailsZeroState';
import EndPointDetails from './EndPointDetails';
@ -19,23 +16,21 @@ function EndPointDetailsWrapper({
endPointName,
setSelectedEndPointName,
domainListFilters,
timeRange,
}: {
domainName: string;
endPointName: string;
setSelectedEndPointName: (value: string) => void;
domainListFilters: IBuilderQuery['filters'];
timeRange: {
startTime: number;
endTime: number;
};
}): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const { startTime: minTime, endTime: maxTime } = timeRange;
const endPointZeroStateQueryPayload = useMemo(
() =>
getEndPointZeroStateQueryPayload(
domainName,
Math.floor(minTime / 1e9),
Math.floor(maxTime / 1e9),
),
() => getEndPointZeroStateQueryPayload(domainName, minTime, maxTime / 1e9),
[domainName, minTime, maxTime],
);
@ -73,6 +68,7 @@ function EndPointDetailsWrapper({
endPointName={endPointName}
setSelectedEndPointName={setSelectedEndPointName}
domainListFilters={domainListFilters}
timeRange={timeRange}
/>
);
}

View File

@ -52,13 +52,11 @@ function DomainMetrics({ domainData }: { domainData: any }): JSX.Element {
<Tooltip title={domainData.errorRate}>
<Progress
status="active"
percent={Number((domainData.errorRate * 100).toFixed(1))}
percent={Number(domainData.errorRate.toFixed(1))}
strokeLinecap="butt"
size="small"
strokeColor={((): string => {
const errorRatePercent = Number(
(domainData.errorRate * 100).toFixed(1),
);
const errorRatePercent = Number(domainData.errorRate.toFixed(1));
if (errorRatePercent >= 90) return Color.BG_SAKURA_500;
if (errorRatePercent >= 60) return Color.BG_AMBER_500;
return Color.BG_FOREST_500;

View File

@ -2,7 +2,13 @@ import { Card } from 'antd';
import GridCard from 'container/GridCardLayout/GridCard';
import { Widgets } from 'types/api/dashboard/getAll';
function MetricOverTimeGraph({ widget }: { widget: Widgets }): JSX.Element {
function MetricOverTimeGraph({
widget,
timeRange,
}: {
widget: Widgets;
timeRange: { startTime: number; endTime: number };
}): JSX.Element {
return (
<div>
<Card bordered className="endpoint-details-card">
@ -12,6 +18,8 @@ function MetricOverTimeGraph({ widget }: { widget: Widgets }): JSX.Element {
isQueryEnabled
onDragSelect={(): void => {}}
customOnDragSelect={(): void => {}}
start={timeRange.startTime}
end={timeRange.endTime}
/>
</div>
</Card>

View File

@ -21,12 +21,9 @@ import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useCallback, useMemo, useRef, useState } from 'react';
import { UseQueryResult } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime';
import { Options } from 'uplot';
import ErrorState from './ErrorState';
@ -38,6 +35,7 @@ function StatusCodeBarCharts({
endPointName,
domainListFilters,
filters,
timeRange,
}: {
endPointStatusCodeBarChartsDataQuery: UseQueryResult<
SuccessResponse<any>,
@ -51,6 +49,10 @@ function StatusCodeBarCharts({
endPointName: string;
domainListFilters: IBuilderQuery['filters'];
filters: IBuilderQuery['filters'];
timeRange: {
startTime: number;
endTime: number;
};
}): JSX.Element {
// 0 : Status Code Count
// 1 : Status Code Latency
@ -64,9 +66,7 @@ function StatusCodeBarCharts({
data: endPointStatusCodeLatencyBarChartsData,
} = endPointStatusCodeLatencyBarChartsDataQuery;
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const { startTime: minTime, endTime: maxTime } = timeRange;
const graphRef = useRef<HTMLDivElement>(null);
const dimensions = useResizeObserver(graphRef);
@ -182,8 +182,8 @@ function StatusCodeBarCharts({
yAxisUnit: statusCodeWidgetInfo[currentWidgetInfoIndex].yAxisUnit,
softMax: null,
softMin: null,
minTimeScale: Math.floor(minTime / 1e9),
maxTimeScale: Math.floor(maxTime / 1e9),
minTimeScale: minTime,
maxTimeScale: maxTime,
panelType: PANEL_TYPES.BAR,
onClickHandler: graphClickHandler,
customSeries: getCustomSeries,

View File

@ -2604,18 +2604,16 @@ export const dependentServicesColumns: ColumnType<DependentServicesData>[] = [
<Progress
status="active"
percent={Number(
(
((errorPercentage === 'n/a' || errorPercentage === '-'
? 0
: errorPercentage) as number) * 100
).toFixed(1),
: errorPercentage) as number).toFixed(1),
)}
strokeLinecap="butt"
size="small"
strokeColor={((): // eslint-disable-next-line sonarjs/no-identical-functions
string => {
const errorPercentagePercent = Number(
((errorPercentage as number) * 100).toFixed(1),
(errorPercentage as number).toFixed(1),
);
if (errorPercentagePercent >= 90) return Color.BG_SAKURA_500;
if (errorPercentagePercent >= 60) return Color.BG_AMBER_500;