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

View File

@ -12,6 +12,12 @@
background: var(--bg-ink-300); background: var(--bg-ink-300);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); 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 { .domain-detail-drawer {

View File

@ -3,16 +3,27 @@ import './DomainDetails.styles.scss';
import { Color, Spacing } from '@signozhq/design-tokens'; import { Color, Spacing } from '@signozhq/design-tokens';
import { Button, Divider, Drawer, Radio, Typography } from 'antd'; import { Button, Divider, Drawer, Radio, Typography } from 'antd';
import { RadioChangeEvent } from 'antd/lib'; 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 { useIsDarkMode } from 'hooks/useDarkMode';
import GetMinMax from 'lib/getMinMax';
import { ArrowDown, ArrowUp, X } from 'lucide-react'; 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 { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime';
import AllEndPoints from './AllEndPoints'; import AllEndPoints from './AllEndPoints';
import DomainMetrics from './components/DomainMetrics'; import DomainMetrics from './components/DomainMetrics';
import { VIEW_TYPES, VIEWS } from './constants'; import { VIEW_TYPES, VIEWS } from './constants';
import EndPointDetailsWrapper from './EndPointDetailsWrapper'; import EndPointDetailsWrapper from './EndPointDetailsWrapper';
const TimeRangeOffset = 1000000000;
function DomainDetails({ function DomainDetails({
domainData, domainData,
handleClose, handleClose,
@ -39,6 +50,49 @@ function DomainDetails({
setSelectedView(e.target.value); 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 ( return (
<Drawer <Drawer
width="60%" width="60%"
@ -50,32 +104,42 @@ function DomainDetails({
{domainData.domainName} {domainData.domainName}
</Typography.Text> </Typography.Text>
</div> </div>
<Button.Group className="domain-details-drawer-header-ctas"> <div className="domain-details-drawer-header-right-container">
<Button <DateTimeSelectionV2
className="domain-navigate-cta" showAutoRefresh={false}
onClick={(): void => { showRefreshText={false}
setSelectedDomainIndex(selectedDomainIndex - 1); onTimeChange={handleTimeChange}
setSelectedEndPointName(''); defaultRelativeTime="5m"
setEndPointsGroupBy([]); isModalTimeSelection
setSelectedView(VIEW_TYPES.ALL_ENDPOINTS); modalSelectedInterval={selectedInterval}
}}
icon={<ArrowUp size={16} />}
disabled={selectedDomainIndex === 0}
title="Previous domain"
/> />
<Button <Button.Group className="domain-details-drawer-header-ctas">
className="domain-navigate-cta" <Button
onClick={(): void => { className="domain-navigate-cta"
setSelectedDomainIndex(selectedDomainIndex + 1); onClick={(): void => {
setSelectedEndPointName(''); setSelectedDomainIndex(selectedDomainIndex - 1);
setEndPointsGroupBy([]); setSelectedEndPointName('');
setSelectedView(VIEW_TYPES.ALL_ENDPOINTS); setEndPointsGroupBy([]);
}} setSelectedView(VIEW_TYPES.ALL_ENDPOINTS);
icon={<ArrowDown size={16} />} }}
disabled={selectedDomainIndex === domainListLength - 1} icon={<ArrowUp size={16} />}
title="Next domain" disabled={selectedDomainIndex === 0}
/> title="Previous domain"
</Button.Group> />
<Button
className="domain-navigate-cta"
onClick={(): void => {
setSelectedDomainIndex(selectedDomainIndex + 1);
setSelectedEndPointName('');
setEndPointsGroupBy([]);
setSelectedView(VIEW_TYPES.ALL_ENDPOINTS);
}}
icon={<ArrowDown size={16} />}
disabled={selectedDomainIndex === domainListLength - 1}
title="Next domain"
/>
</Button.Group>
</div>
</div> </div>
} }
placement="right" placement="right"
@ -126,6 +190,7 @@ function DomainDetails({
setSelectedView={setSelectedView} setSelectedView={setSelectedView}
groupBy={endPointsGroupBy} groupBy={endPointsGroupBy}
setGroupBy={setEndPointsGroupBy} setGroupBy={setEndPointsGroupBy}
timeRange={modalTimeRange}
/> />
)} )}
@ -135,6 +200,7 @@ function DomainDetails({
endPointName={selectedEndPointName} endPointName={selectedEndPointName}
setSelectedEndPointName={setSelectedEndPointName} setSelectedEndPointName={setSelectedEndPointName}
domainListFilters={domainListFilters} domainListFilters={domainListFilters}
timeRange={modalTimeRange}
/> />
)} )}
</> </>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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