From 746382dbfb0310a601e6a32fb5ec3362cc783e7d Mon Sep 17 00:00:00 2001 From: Mike Shi Date: Fri, 5 Jan 2024 11:36:08 -0800 Subject: [PATCH] Endpoint dashboard, fix line chart colors, add search custom columns, fix table column number format --- packages/app/src/ChartUtils.tsx | 23 ++ packages/app/src/EndpointLatencyTile.tsx | 107 ++++++++ packages/app/src/HDXListBarChart.tsx | 103 ++++++++ packages/app/src/HDXMultiSeriesTableChart.tsx | 16 +- packages/app/src/HDXMultiSeriesTimeChart.tsx | 4 +- packages/app/src/LogTableWithSidePanel.tsx | 3 +- packages/app/src/ServiceDashboardPage.tsx | 246 +++++++++++++++++- packages/app/src/types.ts | 2 +- packages/app/src/useDisplayedColumns.ts | 5 +- 9 files changed, 488 insertions(+), 21 deletions(-) create mode 100644 packages/app/src/EndpointLatencyTile.tsx create mode 100644 packages/app/src/HDXListBarChart.tsx diff --git a/packages/app/src/ChartUtils.tsx b/packages/app/src/ChartUtils.tsx index aaee372f0..12045cfc4 100644 --- a/packages/app/src/ChartUtils.tsx +++ b/packages/app/src/ChartUtils.tsx @@ -129,6 +129,8 @@ export function seriesColumns({ })}`, sortOrder: 'sortOrder' in series[0] ? series[0].sortOrder : undefined, + numberFormat: + 'numberFormat' in series[0] ? series[0].numberFormat : undefined, }, ] : series.map((s, i) => { @@ -139,6 +141,7 @@ export function seriesColumns({ showWhere, }), sortOrder: 'sortOrder' in s ? s.sortOrder : undefined, + numberFormat: 'numberFormat' in s ? s.numberFormat : undefined, }; }); @@ -1186,6 +1189,26 @@ export function timeBucketByGranularity( return buckets; } +export const INTEGER_NUMBER_FORMAT: NumberFormat = { + factor: 1, + output: 'number', + mantissa: 0, + thousandSeparated: true, +}; + +export const MS_NUMBER_FORMAT: NumberFormat = { + factor: 1, + output: 'number', + mantissa: 2, + thousandSeparated: true, + unit: 'ms', +}; + +export const ERROR_RATE_PERCENTAGE_NUMBER_FORMAT: NumberFormat = { + output: 'percent', + mantissa: 0, +}; + export const K8S_CPU_PERCENTAGE_NUMBER_FORMAT: NumberFormat = { output: 'percent', mantissa: 0, diff --git a/packages/app/src/EndpointLatencyTile.tsx b/packages/app/src/EndpointLatencyTile.tsx new file mode 100644 index 000000000..26bd77c28 --- /dev/null +++ b/packages/app/src/EndpointLatencyTile.tsx @@ -0,0 +1,107 @@ +import * as React from 'react'; +import { Box, Button, Card, Flex } from '@mantine/core'; + +import { + convertDateRangeToGranularityString, + MS_NUMBER_FORMAT, +} from './ChartUtils'; +import HDXHistogramChart from './HDXHistogramChart'; +import HDXMultiSeriesLineChart from './HDXMultiSeriesTimeChart'; +import { Histogram } from './SVGIcons'; + +export default function EndpointLatencyTile({ + height = 300, + dateRange, + scopeWhereQuery = (where: string) => where, +}: { + height?: number; + dateRange: [Date, Date]; + scopeWhereQuery?: (where: string) => string; +}) { + const [chartType, setChartType] = React.useState<'line' | 'histogram'>( + 'line', + ); + + return ( + + + + Request Latency + + + + + + + + + + + {chartType === 'line' ? ( + + ) : ( + + )} + + + ); +} diff --git a/packages/app/src/HDXListBarChart.tsx b/packages/app/src/HDXListBarChart.tsx new file mode 100644 index 000000000..037f890ff --- /dev/null +++ b/packages/app/src/HDXListBarChart.tsx @@ -0,0 +1,103 @@ +import { memo } from 'react'; +import { Box, Flex, Text } from '@mantine/core'; + +import api from './api'; +import { Granularity } from './ChartUtils'; +import type { ChartSeries, NumberFormat } from './types'; +import { semanticKeyedColor } from './utils'; + +function ListItem({ + title, + value, + color, + percent, +}: { + title: string; + value: string; + color: string; + percent: number; +}) { + return ( + + + {title} + {value} + + + + + + ); +} + +const HDXListBarChart = memo( + ({ + config: { series, seriesReturnType = 'column', dateRange }, + }: { + config: { + series: [ChartSeries]; + granularity: Granularity; + dateRange: [Date, Date]; + seriesReturnType?: 'ratio' | 'column'; + numberFormat?: NumberFormat; + groupColumnName?: string; + }; + onSettled?: () => void; + }) => { + const { data, isError, isLoading } = api.useMultiSeriesChart({ + series, + endDate: dateRange[1] ?? new Date(), + startDate: dateRange[0] ?? new Date(), + seriesReturnType, + }); + + const rows: undefined | any[] = data?.data; + + const values = (rows ?? []).map((row: any) => row['series_0.data']); + const maxValue = Math.max(...values); + const totalValue = values.reduce((a, b) => a + b, 0); + + return isLoading ? ( +
+ Loading Chart Data... +
+ ) : isError ? ( +
+ Error loading chart, please try again or contact support. +
+ ) : data?.data?.length === 0 ? ( +
+ No data found within time range. +
+ ) : ( + + {rows?.map((row: any) => { + const value = row['series_0.data']; + const percentOfMax = (value / maxValue) * 100; + const percentOfTotal = (value / totalValue) * 100; + const group = `${row.group}`; + + return ( + + + + ); + })} + + ); + }, +); + +export default HDXListBarChart; diff --git a/packages/app/src/HDXMultiSeriesTableChart.tsx b/packages/app/src/HDXMultiSeriesTableChart.tsx index 07aa7c339..d8deb5c8a 100644 --- a/packages/app/src/HDXMultiSeriesTableChart.tsx +++ b/packages/app/src/HDXMultiSeriesTableChart.tsx @@ -25,7 +25,6 @@ import { formatNumber } from './utils'; const Table = ({ data, groupColumnName, - numberFormat, columns, getRowSearchLink, onSortClick, @@ -35,9 +34,9 @@ const Table = ({ dataKey: string; displayName: string; sortOrder?: 'asc' | 'desc'; + numberFormat?: NumberFormat; }[]; groupColumnName: string; - numberFormat?: NumberFormat; getRowSearchLink?: (row: any) => string; onSortClick?: (columnNumber: number) => void; }) => { @@ -52,7 +51,7 @@ const Table = ({ header: groupColumnName, size: tableWidth != null ? tableWidth / numColumns : 200, }, - ...columns.map(({ dataKey, displayName }, i) => ({ + ...columns.map(({ dataKey, displayName, numberFormat }, i) => ({ accessorKey: dataKey, header: displayName, accessorFn: (row: any) => row[dataKey], @@ -272,13 +271,7 @@ const Table = ({ const HDXMultiSeriesTableChart = memo( ({ - config: { - series, - seriesReturnType = 'column', - dateRange, - numberFormat, - groupColumnName, - }, + config: { series, seriesReturnType = 'column', dateRange, groupColumnName }, onSettled, onSortClick, }: { @@ -287,8 +280,6 @@ const HDXMultiSeriesTableChart = memo( granularity: Granularity; dateRange: [Date, Date]; seriesReturnType: 'ratio' | 'column'; - sortOrder: 'asc' | 'desc'; - numberFormat?: NumberFormat; groupColumnName?: string; }; onSettled?: () => void; @@ -340,7 +331,6 @@ const HDXMultiSeriesTableChart = memo( : 'Group') } columns={seriesMeta} - numberFormat={numberFormat} getRowSearchLink={getRowSearchLink} onSortClick={onSortClick} /> diff --git a/packages/app/src/HDXMultiSeriesTimeChart.tsx b/packages/app/src/HDXMultiSeriesTimeChart.tsx index a8fe9e081..4a694aa8d 100644 --- a/packages/app/src/HDXMultiSeriesTimeChart.tsx +++ b/packages/app/src/HDXMultiSeriesTimeChart.tsx @@ -67,7 +67,7 @@ const MemoChart = memo(function MemoChart({ type="monotone" dataKey={key} name={lineNames[i] ?? key} - fill={semanticKeyedColor(key)} + fill={semanticKeyedColor(lineNames[i] ?? key)} stackId="1" /> ) : ( @@ -76,7 +76,7 @@ const MemoChart = memo(function MemoChart({ type="monotone" dataKey={key} name={lineNames[i] ?? key} - stroke={semanticKeyedColor(key)} + stroke={semanticKeyedColor(lineNames[i] ?? key)} dot={false} /> ), diff --git a/packages/app/src/LogTableWithSidePanel.tsx b/packages/app/src/LogTableWithSidePanel.tsx index ad59b74e9..803428670 100644 --- a/packages/app/src/LogTableWithSidePanel.tsx +++ b/packages/app/src/LogTableWithSidePanel.tsx @@ -21,6 +21,7 @@ export function LogTableWithSidePanel({ config: { where: string; dateRange: [Date, Date]; + columns?: string[]; }; isUTC: boolean; isLive: boolean; @@ -84,7 +85,7 @@ export function LogTableWithSidePanel({ const voidFn = useCallback(() => {}, []); const { displayedColumns, setDisplayedColumns, toggleColumn } = - useDisplayedColumns(); + useDisplayedColumns(config.columns); return ( <> diff --git a/packages/app/src/ServiceDashboardPage.tsx b/packages/app/src/ServiceDashboardPage.tsx index be7b7d352..d44091268 100644 --- a/packages/app/src/ServiceDashboardPage.tsx +++ b/packages/app/src/ServiceDashboardPage.tsx @@ -4,12 +4,21 @@ import { StringParam, useQueryParam, withDefault } from 'use-query-params'; import { Card, Grid, Group, Select, Tabs } from '@mantine/core'; import AppNav from './AppNav'; -import { convertDateRangeToGranularityString } from './ChartUtils'; +import { + ERROR_RATE_PERCENTAGE_NUMBER_FORMAT, + INTEGER_NUMBER_FORMAT, + MS_NUMBER_FORMAT, + convertDateRangeToGranularityString, +} from './ChartUtils'; import { K8S_CPU_PERCENTAGE_NUMBER_FORMAT, K8S_MEM_NUMBER_FORMAT, } from './ChartUtils'; +import EndpointLatencyTile from './EndpointLatencyTile'; import HDXLineChart from './HDXLineChart'; +import HDXListBarChart from './HDXListBarChart'; +import HDXMultiSeriesTableChart from './HDXMultiSeriesTableChart'; +import HDXMultiSeriesLineChart from './HDXMultiSeriesTimeChart'; import { LogTableWithSidePanel } from './LogTableWithSidePanel'; import SearchInput from './SearchInput'; import SearchTimeRangePicker from './SearchTimeRangePicker'; @@ -108,6 +117,16 @@ export default function ServiceDashboardPage() { ].join(' '); }, [podNames, searchQuery]); + const scopeWhereQuery = React.useCallback( + (where: string) => { + const serviceQuery = service ? `service:${service} ` : ''; + const sQuery = searchQuery ? `(${searchQuery}) ` : ''; + const whereQuery = where ? `(${where})` : ''; + return `${serviceQuery}${sQuery}${whereQuery}`; + }, + [service, searchQuery], + ); + return (
@@ -269,7 +288,230 @@ export default function ServiceDashboardPage() { - HTTP Service + + + + + + Request Error Rate + + + + + + + + + + Request Throughput + + + + + + + + + + 20 Top Most Time Consuming Endpoints + + + + + + + + + + + + + Endpoints + + + + + + + + + + Debug + + +
+                          {JSON.stringify(
+                            {
+                              dateRange,
+                              searchQuery,
+                              service,
+                              podNames,
+                              whereClause,
+                            },
+                            null,
+                            4,
+                          )}
+                        
+
+
+
+
+
Database
diff --git a/packages/app/src/types.ts b/packages/app/src/types.ts index dbd46101e..0b0a8facc 100644 --- a/packages/app/src/types.ts +++ b/packages/app/src/types.ts @@ -198,7 +198,7 @@ export type TableChartSeries = { type: 'table'; table: SourceTable; aggFn: AggFn; - field: string | undefined; + field?: string | undefined; where: string; groupBy: string[]; sortOrder?: 'desc' | 'asc'; diff --git a/packages/app/src/useDisplayedColumns.ts b/packages/app/src/useDisplayedColumns.ts index bf9978b43..bd8212905 100644 --- a/packages/app/src/useDisplayedColumns.ts +++ b/packages/app/src/useDisplayedColumns.ts @@ -1,8 +1,9 @@ import { useState } from 'react'; // TODO: Instead of prop drilling additional columns, we can consider using React.Context or Jotai -export const useDisplayedColumns = () => { - const [displayedColumns, setDisplayedColumns] = useState([]); +export const useDisplayedColumns = (initialColumns: string[] = []) => { + const [displayedColumns, setDisplayedColumns] = + useState(initialColumns); const toggleColumn = (column: string) => { if (displayedColumns.includes(column)) {