mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
## Summary Large refactor changing the TSource type to a true discriminated union. This means that the expected fields for `kind: 'log'` will differ from those for `'trace', 'session', 'metrics'`. This avoids the current laissez faire source type that currently exists, and required extensive changes across the api and app packages. Also includes a nice addition to `useSource` - you can now specify a `kind` field, which will properly infer the type of the returned source. This also makes use of discriminators in mongoose. This does change a bit of the way that we create and update sources. Obvious changes to sources have also been made, namely making `timeValueExpression` required on sources. Care has been taken to avoid requiring a migration. ### How to test locally or on Vercel 1. `yarn dev` 2. Play around with the app, especially around source creation, source edits, and loading existing sources from a previous version ### References - Linear Issue: References HDX-3352 - Related PRs: Ref: HDX-3352
217 lines
7.3 KiB
TypeScript
217 lines
7.3 KiB
TypeScript
import { useCallback, useMemo } from 'react';
|
|
import { pick } from 'lodash';
|
|
import { parseAsString, useQueryState } from 'nuqs';
|
|
import {
|
|
DisplayType,
|
|
type Filter,
|
|
SourceKind,
|
|
} from '@hyperdx/common-utils/dist/types';
|
|
import { Drawer, Grid, Text } from '@mantine/core';
|
|
import { IconServer } from '@tabler/icons-react';
|
|
|
|
import {
|
|
ERROR_RATE_PERCENTAGE_NUMBER_FORMAT,
|
|
INTEGER_NUMBER_FORMAT,
|
|
} from '@/ChartUtils';
|
|
import { ChartBox } from '@/components/ChartBox';
|
|
import { DBTimeChart } from '@/components/DBTimeChart';
|
|
import { DrawerBody, DrawerHeader } from '@/components/DrawerUtils';
|
|
import ServiceDashboardEndpointPerformanceChart from '@/components/ServiceDashboardEndpointPerformanceChart';
|
|
import SlowestEventsTile from '@/components/ServiceDashboardSlowestEventsTile';
|
|
import { useServiceDashboardExpressions } from '@/serviceDashboard';
|
|
import { EndpointLatencyChart } from '@/ServicesDashboardPage';
|
|
import { useSource } from '@/source';
|
|
import { useZIndex, ZIndexContext } from '@/zIndex';
|
|
|
|
import styles from '@/../styles/LogSidePanel.module.scss';
|
|
|
|
export default function ServiceDashboardEndpointSidePanel({
|
|
sourceId,
|
|
service,
|
|
searchedTimeRange,
|
|
}: {
|
|
sourceId?: string;
|
|
service?: string;
|
|
searchedTimeRange: [Date, Date];
|
|
}) {
|
|
const { data: source } = useSource({
|
|
id: sourceId,
|
|
kinds: [SourceKind.Trace],
|
|
});
|
|
const { expressions } = useServiceDashboardExpressions({ source });
|
|
|
|
const [endpoint, setEndpoint] = useQueryState('endpoint', parseAsString);
|
|
const onClose = useCallback(() => {
|
|
setEndpoint(null);
|
|
}, [setEndpoint]);
|
|
|
|
const contextZIndex = useZIndex();
|
|
const drawerZIndex = contextZIndex + 10;
|
|
|
|
const endpointFilters = useMemo(() => {
|
|
if (!expressions) return [];
|
|
|
|
const filters: Filter[] = [
|
|
{
|
|
type: 'sql',
|
|
condition: `${expressions.endpoint} IN ('${endpoint}') AND ${expressions.isSpanKindServer}`,
|
|
},
|
|
];
|
|
if (service) {
|
|
filters.push({
|
|
type: 'sql',
|
|
condition: `${expressions.service} IN ('${service}')`,
|
|
});
|
|
}
|
|
return filters;
|
|
}, [endpoint, service, expressions]);
|
|
|
|
if (!endpoint || !source) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<Drawer
|
|
opened
|
|
onClose={onClose}
|
|
position="right"
|
|
size="80vw"
|
|
withCloseButton={false}
|
|
zIndex={drawerZIndex}
|
|
styles={{
|
|
body: {
|
|
padding: 0,
|
|
},
|
|
}}
|
|
>
|
|
<ZIndexContext.Provider value={drawerZIndex}>
|
|
<div className={styles.panel}>
|
|
<DrawerHeader
|
|
header={
|
|
<>
|
|
Details for {endpoint}
|
|
{service && (
|
|
<Text component="span" c="gray" fz="xs">
|
|
<IconServer size={14} className="ms-3 me-1" />
|
|
{service}
|
|
</Text>
|
|
)}
|
|
</>
|
|
}
|
|
onClose={onClose}
|
|
/>
|
|
<DrawerBody>
|
|
<Grid grow={false} w="100%" maw="100%" overflow="hidden">
|
|
<Grid.Col span={6}>
|
|
<ChartBox style={{ height: 350 }}>
|
|
{source && expressions && (
|
|
<DBTimeChart
|
|
title="Request Error Rate"
|
|
sourceId={source.id}
|
|
hiddenSeries={['total_count', 'error_count']}
|
|
config={{
|
|
source: source.id,
|
|
...pick(source, [
|
|
'timestampValueExpression',
|
|
'connection',
|
|
'from',
|
|
]),
|
|
where: '',
|
|
whereLanguage: 'sql',
|
|
select: [
|
|
// Separate the aggregations from the conversion to rate so that AggregatingMergeTree MVs can be used
|
|
{
|
|
valueExpression: '',
|
|
aggFn: 'count',
|
|
alias: 'error_count',
|
|
aggCondition: expressions.isError,
|
|
aggConditionLanguage: 'sql',
|
|
},
|
|
{
|
|
valueExpression: '',
|
|
aggFn: 'count',
|
|
alias: 'total_count',
|
|
},
|
|
{
|
|
valueExpression: `error_count / total_count`,
|
|
alias: 'Error Rate %',
|
|
},
|
|
],
|
|
numberFormat: ERROR_RATE_PERCENTAGE_NUMBER_FORMAT,
|
|
filters: endpointFilters,
|
|
dateRange: searchedTimeRange,
|
|
}}
|
|
showDisplaySwitcher={false}
|
|
/>
|
|
)}
|
|
</ChartBox>
|
|
</Grid.Col>
|
|
<Grid.Col span={6}>
|
|
<ChartBox style={{ height: 350 }}>
|
|
{source && expressions && (
|
|
<DBTimeChart
|
|
title="Request Throughput"
|
|
sourceId={source.id}
|
|
config={{
|
|
source: source.id,
|
|
...pick(source, [
|
|
'timestampValueExpression',
|
|
'connection',
|
|
'from',
|
|
]),
|
|
where: '',
|
|
whereLanguage: 'sql',
|
|
select: [
|
|
{
|
|
aggFn: 'count' as const,
|
|
valueExpression: 'value',
|
|
alias: 'Requests',
|
|
aggCondition: '',
|
|
aggConditionLanguage: 'sql',
|
|
},
|
|
],
|
|
displayType: DisplayType.Line,
|
|
numberFormat: {
|
|
...INTEGER_NUMBER_FORMAT,
|
|
unit: 'requests',
|
|
},
|
|
filters: endpointFilters,
|
|
dateRange: searchedTimeRange,
|
|
}}
|
|
/>
|
|
)}
|
|
</ChartBox>
|
|
</Grid.Col>
|
|
<Grid.Col span={6}>
|
|
<ServiceDashboardEndpointPerformanceChart
|
|
source={source}
|
|
dateRange={searchedTimeRange}
|
|
service={service}
|
|
endpoint={endpoint}
|
|
/>
|
|
</Grid.Col>
|
|
<Grid.Col span={6}>
|
|
<EndpointLatencyChart
|
|
source={source}
|
|
dateRange={searchedTimeRange}
|
|
extraFilters={endpointFilters}
|
|
/>
|
|
</Grid.Col>
|
|
<Grid.Col span={12}>
|
|
{/* Ensure expressions exists to ensure that endpointFilters has set */}
|
|
{expressions && (
|
|
<SlowestEventsTile
|
|
title="Slowest 5% of Transactions"
|
|
source={source}
|
|
dateRange={searchedTimeRange}
|
|
extraFilters={endpointFilters}
|
|
/>
|
|
)}
|
|
</Grid.Col>
|
|
</Grid>
|
|
</DrawerBody>
|
|
</div>
|
|
</ZIndexContext.Provider>
|
|
</Drawer>
|
|
);
|
|
}
|