feat: add network traffic charting

This commit is contained in:
TzeYiing committed 2023-05-24 15:37:05 +08:00
1 parent 3dbc231a3f
commit 68dca39bf5
8 files changed
+139 -28

No files matched your search

@@ -75,8 +75,8 @@ export const PRESET_CONFIG: Record<Presets, PresetConfig> = {
timestamp ASC`,
},
topRoutes: {
queryType: "logs",
sql: (filters)=> `
queryType: 'logs',
sql: (filters) => `
select
request.path as path,
request.method as method,
@@ -95,7 +95,7 @@ export const PRESET_CONFIG: Record<Presets, PresetConfig> = {
count desc
limit
3
`
`,
},
errorCounts: {
queryType: 'logs',
@@ -118,8 +118,8 @@ export const PRESET_CONFIG: Record<Presets, PresetConfig> = {
`,
},
topErrorRoutes: {
queryType: "logs",
sql: (filters)=> `
queryType: 'logs',
sql: (filters) => `
select
request.path as path,
request.method as method,
@@ -140,7 +140,7 @@ export const PRESET_CONFIG: Record<Presets, PresetConfig> = {
count desc
limit
3
`
`,
},
responseSpeed: {
queryType: 'logs',
@@ -162,8 +162,8 @@ export const PRESET_CONFIG: Record<Presets, PresetConfig> = {
`,
},
topSlowRoutes: {
queryType: "logs",
sql: (filters)=> `
queryType: 'logs',
sql: (filters) => `
select
request.path as path,
request.method as method,
@@ -183,7 +183,44 @@ export const PRESET_CONFIG: Record<Presets, PresetConfig> = {
avg desc
limit
3
`
`,
},
networkTraffic: {
queryType: 'logs',
sql: (filters) => `
select
cast(timestamp_trunc(t.timestamp, hour) as datetime) as timestamp,
coalesce(
safe_divide(
sum(
cast(coalesce(headers.content_length, "0") as int64)
),
1000000
),
0
) as ingress_mb,
coalesce(
safe_divide(
sum(
cast(coalesce(resp_headers.content_length, "0") as int64)
),
1000000
),
0
) as egress_mb,
FROM
edge_logs t
cross join unnest(metadata) as m
cross join unnest(m.response) as response
cross join unnest(m.request) as request
cross join unnest(request.headers) as headers
cross join unnest(response.headers) as resp_headers
${generateRexepWhere(filters)}
GROUP BY
timestamp
ORDER BY
timestamp ASC
`,
},
},
},
@@ -9,7 +9,59 @@ import { Button, Collapsible, IconChevronRight } from 'ui'
import { queryParamsToObject } from '../Reports.utils'
import { Fragment } from 'react'
import useFillTimeseriesSorted from 'hooks/analytics/useFillTimeseriesSorted'
import sumBy from 'lodash/sumBy'
export const networkTrafficRenderer = (
props: ReportWidgetProps<{
timestamp: string
ingress: number
egress: number
}>
) => {
const data = useFillTimeseriesSorted(
props.data,
'timestamp',
['ingress_mb', 'egress_mb'],
0,
props.params?.iso_timestamp_start,
props.params?.iso_timestamp_end
)
const totalIngress = sumBy(props.data, 'ingress_mb')
const totalEgress = sumBy(props.data, 'egress_mb')
function determinePrecision(valueInMb: number) {
return valueInMb < 0.001 ? 7 : totalIngress > 1 ? 2 : 4
}
return (
<div className="flex flex-col gap-2 w-full">
<BarChart
size="small"
title="Ingress"
highlightedValue={sumBy(props.data, 'ingress_mb')}
format="MB"
className="w-full"
valuePrecision={determinePrecision(totalIngress)}
data={data}
yAxisKey="ingress_mb"
xAxisKey="timestamp"
displayDateInUtc
/>
<BarChart
size="small"
title="Egress"
highlightedValue={totalEgress}
format="MB"
valuePrecision={determinePrecision(totalEgress)}
className="w-full"
data={data}
yAxisKey="egress_mb"
xAxisKey="timestamp"
displayDateInUtc
/>
</div>
)
}
export const TotalRequestsChartRenderer = (
props: ReportWidgetProps<{
timestamp: string
+4 -3
View File
@@ -24,7 +24,7 @@ export interface AreaChartProps<D = Datum> extends CommonChartProps<D> {
displayDateInUtc?: boolean
}
const AreaChart: React.FC<AreaChartProps> = ({
const AreaChart = ({
data,
yAxisKey,
xAxisKey,
@@ -36,8 +36,9 @@ const AreaChart: React.FC<AreaChartProps> = ({
displayDateInUtc,
minimalHeader,
className = '',
valuePrecision,
size = 'normal',
}) => {
}: AreaChartProps) => {
const { Container } = useChartSize(size)
const [focusDataIndex, setFocusDataIndex] = useState<number | null>(null)
@@ -62,7 +63,7 @@ const AreaChart: React.FC<AreaChartProps> = ({
customDateFormat={customDateFormat}
highlightedValue={
typeof resolvedHighlightedValue === 'number'
? numberFormatter(resolvedHighlightedValue)
? numberFormatter(resolvedHighlightedValue, valuePrecision)
: resolvedHighlightedValue
}
highlightedLabel={resolvedHighlightedLabel}
+4 -4
View File
@@ -13,13 +13,12 @@ dayjs.extend(utc)
export interface BarChartProps<D = Datum> extends CommonChartProps<D> {
yAxisKey: string
xAxisKey: string
format?: string
customDateFormat?: string
displayDateInUtc?: boolean
onBarClick?: (datum: Datum, tooltipData?: Parameters<CategoricalChartProps['onClick']>[0]) => void
}
const BarChart: React.FC<BarChartProps> = ({
const BarChart = ({
data,
yAxisKey,
xAxisKey,
@@ -30,10 +29,11 @@ const BarChart: React.FC<BarChartProps> = ({
highlightedLabel,
displayDateInUtc,
minimalHeader,
valuePrecision,
className = '',
size = 'normal',
onBarClick,
}) => {
}: BarChartProps) => {
const { Container } = useChartSize(size)
const [focusDataIndex, setFocusDataIndex] = useState<number | null>(null)
@@ -58,7 +58,7 @@ const BarChart: React.FC<BarChartProps> = ({
customDateFormat={customDateFormat}
highlightedValue={
typeof resolvedHighlightedValue === 'number'
? numberFormatter(resolvedHighlightedValue)
? numberFormatter(resolvedHighlightedValue, valuePrecision)
: resolvedHighlightedValue
}
highlightedLabel={resolvedHighlightedLabel}
+8 -8
View File
@@ -1,8 +1,6 @@
import { DateTimeFormats } from './Charts.constants'
export interface ChartHeaderProps {
title?: string
format?: string
format?: string | ((value: unknown) => string)
customDateFormat?: string
minimalHeader?: boolean
displayDateInUtc?: boolean
@@ -15,7 +13,7 @@ const ChartHeader: React.FC<ChartHeaderProps> = ({
highlightedLabel,
title,
minimalHeader = false,
}) => {
}: ChartHeaderProps) => {
const chartTitle = (
<h3 className={'text-scale-900 ' + (minimalHeader ? 'text-xs' : 'text-sm')}>{title}</h3>
)
@@ -24,8 +22,10 @@ const ChartHeader: React.FC<ChartHeaderProps> = ({
<h5
className={`text-scale-1200 text-xl font-normal ${minimalHeader ? 'text-base' : 'text-2xl'}`}
>
{highlightedValue}
<span className="text-lg">{format}</span>
{highlightedValue !== undefined && String(highlightedValue)}
<span className="text-lg">
{typeof format === 'function' ? format(highlightedValue) : format}
</span>
</h5>
)
const label = <h5 className="text-scale-900 text-xs">{highlightedLabel}</h5>
@@ -35,7 +35,7 @@ const ChartHeader: React.FC<ChartHeaderProps> = ({
<div className="flex flex-row items-center gap-x-4" style={{ minHeight: '1.8rem' }}>
{title && chartTitle}
<div className="flex flex-row items-baseline gap-x-2">
{highlightedValue && highlighted}
{highlightedValue !== undefined && highlighted}
{label}
</div>
</div>
@@ -45,7 +45,7 @@ const ChartHeader: React.FC<ChartHeaderProps> = ({
return (
<div className="h-16">
{title && chartTitle}
{highlightedValue && highlighted}
{highlightedValue !== undefined && highlighted}
{label}
</div>
)
+2 -1
View File
@@ -13,6 +13,7 @@ export interface CommonChartProps<D>
> {
title?: string
className?: string
valuePrecision?: number
size?: 'tiny' | 'small' | 'normal' | 'large'
}
@@ -27,7 +28,7 @@ export interface StackedChartProps<D> extends CommonChartProps<D> {
export type HeaderType<D> = {
attribute: string
focus: number | null
format?: string
format?: string | ((value: unknown) => string);
highlightedValue?: number | string
highlightedLabel?: string
data: D[]
@@ -13,6 +13,7 @@ import {
ErrorCountsChartRenderer,
ResponseSpeedChartRenderer,
TopApiRoutesRenderer,
networkTrafficRenderer,
} from 'components/interfaces/Reports/renderers/ApiRenderers'
import { useState, useEffect } from 'react'
import ReportHeader from 'components/interfaces/Reports/ReportHeader'
@@ -88,6 +89,15 @@ export const ApiReport: NextPageWithLayout = () => {
appendProps={{ data: report.data.topSlowRoutes || [] }}
append={TopApiRoutesRenderer}
/>
<ReportWidget
isLoading={report.isLoading}
params={report.params.networkTraffic}
title="Network Traffic"
tooltip="Ingress and egress of requests and responses respectively"
data={report.data.networkTraffic || []}
renderer={networkTrafficRenderer}
/>
</ReportPadding>
)
}
@@ -106,6 +116,7 @@ const useApiReport = () => {
const topErrorRoutes = queryHooks.topErrorRoutes()
const responseSpeed = queryHooks.responseSpeed()
const topSlowRoutes = queryHooks.topSlowRoutes()
const networkTraffic = queryHooks.networkTraffic()
const activeHooks = [
totalRequests,
topRoutes,
@@ -113,6 +124,7 @@ const useApiReport = () => {
topErrorRoutes,
responseSpeed,
topSlowRoutes,
networkTraffic,
]
const [filters, setFilters] = useState<ReportFilterItem[]>([])
const addFilter = (filter: ReportFilterItem) => {
@@ -161,17 +173,21 @@ const useApiReport = () => {
if (topSlowRoutes.changeQuery) {
topSlowRoutes.changeQuery(PRESET_CONFIG.api.queries.topSlowRoutes.sql(filters))
}
if (networkTraffic.changeQuery) {
networkTraffic.changeQuery(PRESET_CONFIG.api.queries.networkTraffic.sql(filters))
}
}, [JSON.stringify(filters)])
const handleRefresh = async () => {
activeHooks.forEach(hook => hook.runQuery())
activeHooks.forEach((hook) => hook.runQuery())
}
const handleSetParams = (params: Partial<LogsEndpointParams>) => {
activeHooks.forEach(hook => {
activeHooks.forEach((hook) => {
hook.setParams?.((prev: LogsEndpointParams) => ({ ...prev, ...params }))
})
}
const isLoading = activeHooks.some(hook => hook.isLoading)
const isLoading = activeHooks.some((hook) => hook.isLoading)
return {
data: {
totalRequests: totalRequests.logData,
@@ -180,6 +196,7 @@ const useApiReport = () => {
topRoutes: topRoutes.logData,
topErrorRoutes: topErrorRoutes.logData,
topSlowRoutes: topSlowRoutes.logData,
networkTraffic: networkTraffic.logData,
},
params: {
totalRequests: totalRequests.params,
@@ -188,6 +205,7 @@ const useApiReport = () => {
topRoutes: topRoutes.params,
topErrorRoutes: topErrorRoutes.params,
topSlowRoutes: topSlowRoutes.params,
networkTraffic: networkTraffic.params,
},
mergeParams: handleSetParams,
filters,
@@ -15,6 +15,8 @@ test(`static elements`, async () => {
await screen.findByText('Total Requests')
await screen.findByText('Response Errors')
await screen.findByText('Response Speed')
await screen.findByText('Network Ingress')
await screen.findByText('Network Egress')
await screen.findByText(/Last 24 hours/)
await screen.findByText(/Custom/)
})