mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
feat: Show resource usage in function metrics (#18956)
* feat: show resource usage charts in function metrics * fix: show highlighted value in StackedBarChart * fix: PR feedback updates
This commit is contained in:
1 parent
d42ac302f9
commit
ca8007ec80
5 files changed
+453
-49
No files matched your search
@@ -1,5 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import { Bar, BarChart, Cell, Legend, Tooltip, XAxis } from 'recharts'
|
||||
import dayjs from 'dayjs'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
import ChartHeader from './ChartHeader'
|
||||
import {
|
||||
CHART_COLORS,
|
||||
@@ -9,8 +11,16 @@ import {
|
||||
ValidStackColor,
|
||||
} from './Charts.constants'
|
||||
import { CommonChartProps } from './Charts.types'
|
||||
import { precisionFormatter, timestampFormatter, useChartSize, useStacked } from './Charts.utils'
|
||||
import {
|
||||
numberFormatter,
|
||||
precisionFormatter,
|
||||
timestampFormatter,
|
||||
useChartSize,
|
||||
useStacked,
|
||||
} from './Charts.utils'
|
||||
import NoDataPlaceholder from './NoDataPlaceholder'
|
||||
dayjs.extend(utc)
|
||||
|
||||
interface Props extends CommonChartProps<any> {
|
||||
xAxisKey: string
|
||||
yAxisKey: string
|
||||
@@ -31,8 +41,11 @@ const StackedBarChart: React.FC<Props> = ({
|
||||
yAxisKey,
|
||||
customDateFormat = DateTimeFormats.FULL,
|
||||
title,
|
||||
highlightedValue,
|
||||
highlightedLabel,
|
||||
format,
|
||||
minimalHeader = false,
|
||||
valuePrecision,
|
||||
onBarClick,
|
||||
variant,
|
||||
xAxisFormatAsDate = true,
|
||||
@@ -50,6 +63,18 @@ const StackedBarChart: React.FC<Props> = ({
|
||||
variant,
|
||||
})
|
||||
const [focusDataIndex, setFocusDataIndex] = useState<number | null>(null)
|
||||
|
||||
const day = (value: number | string) => (displayDateInUtc ? dayjs(value).utc() : dayjs(value))
|
||||
const resolvedHighlightedLabel =
|
||||
(focusDataIndex !== null &&
|
||||
data &&
|
||||
data[focusDataIndex] !== undefined &&
|
||||
day(data[focusDataIndex][xAxisKey]).format(customDateFormat)) ||
|
||||
highlightedLabel
|
||||
|
||||
const resolvedHighlightedValue =
|
||||
focusDataIndex !== null ? data[focusDataIndex]?.[yAxisKey] : highlightedValue
|
||||
|
||||
if (!data || data.length === 0) return <NoDataPlaceholder size={size} />
|
||||
const stackColorScales = genStackColorScales(stackColors)
|
||||
return (
|
||||
@@ -60,6 +85,12 @@ const StackedBarChart: React.FC<Props> = ({
|
||||
format={format}
|
||||
customDateFormat={customDateFormat}
|
||||
minimalHeader={minimalHeader}
|
||||
highlightedValue={
|
||||
typeof resolvedHighlightedValue === 'number'
|
||||
? numberFormatter(resolvedHighlightedValue, valuePrecision)
|
||||
: resolvedHighlightedValue
|
||||
}
|
||||
highlightedLabel={resolvedHighlightedLabel}
|
||||
/>
|
||||
)}
|
||||
<Container>
|
||||
@@ -137,7 +168,11 @@ const StackedBarChart: React.FC<Props> = ({
|
||||
}}
|
||||
cursor={false}
|
||||
labelClassName="text-white"
|
||||
contentStyle={{ backgroundColor: '#444444', borderColor: '#444444', fontSize: '12px' }}
|
||||
contentStyle={{
|
||||
backgroundColor: '#444444',
|
||||
borderColor: '#444444',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
wrapperClassName="bg-gray-600 rounded min-w-md"
|
||||
/>
|
||||
</BarChart>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { analyticsKeys } from './keys'
|
||||
|
||||
export type FunctionsReqStatsVariables = {
|
||||
projectRef?: string
|
||||
functionId?: string
|
||||
interval?: string
|
||||
}
|
||||
|
||||
export type FunctionsReqStatsResponse = any
|
||||
|
||||
export async function getFunctionsReqStats(
|
||||
{ projectRef, functionId, interval }: FunctionsReqStatsVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!projectRef) {
|
||||
throw new Error('projectRef is required')
|
||||
}
|
||||
if (!functionId) {
|
||||
throw new Error('functionId is required')
|
||||
}
|
||||
if (!interval) {
|
||||
throw new Error('interval is required')
|
||||
}
|
||||
|
||||
const response = await get<FunctionsReqStatsResponse>(
|
||||
`${API_URL}/projects/${projectRef}/analytics/endpoints/functions.req-stats?interval=${interval}&function_id=${functionId}`,
|
||||
{
|
||||
signal,
|
||||
}
|
||||
)
|
||||
if (response.error) {
|
||||
throw response.error
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
export type FunctionsReqStatsData = Awaited<ReturnType<typeof getFunctionsReqStats>>
|
||||
export type FunctionsReqStatsError = unknown
|
||||
|
||||
export const useFunctionsReqStatsQuery = <TData = FunctionsReqStatsData>(
|
||||
{ projectRef, functionId, interval }: FunctionsReqStatsVariables,
|
||||
{
|
||||
enabled = true,
|
||||
...options
|
||||
}: UseQueryOptions<FunctionsReqStatsData, FunctionsReqStatsError, TData> = {}
|
||||
) =>
|
||||
useQuery<FunctionsReqStatsData, FunctionsReqStatsError, TData>(
|
||||
analyticsKeys.functionsReqStats(projectRef, { functionId, interval }),
|
||||
({ signal }) => getFunctionsReqStats({ projectRef, functionId, interval }, signal),
|
||||
{
|
||||
enabled:
|
||||
enabled &&
|
||||
typeof projectRef !== 'undefined' &&
|
||||
typeof functionId !== 'undefined' &&
|
||||
typeof interval !== 'undefined',
|
||||
...options,
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { analyticsKeys } from './keys'
|
||||
|
||||
export type FunctionsResourceUsageVariables = {
|
||||
projectRef?: string
|
||||
functionId?: string
|
||||
interval?: string
|
||||
}
|
||||
|
||||
export type FunctionsResourceUsageResponse = any
|
||||
|
||||
export async function getFunctionsResourceUsage(
|
||||
{ projectRef, functionId, interval }: FunctionsResourceUsageVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!projectRef) {
|
||||
throw new Error('projectRef is required')
|
||||
}
|
||||
if (!functionId) {
|
||||
throw new Error('functionId is required')
|
||||
}
|
||||
if (!interval) {
|
||||
throw new Error('interval is required')
|
||||
}
|
||||
|
||||
const response = await get<FunctionsResourceUsageResponse>(
|
||||
`${API_URL}/projects/${projectRef}/analytics/endpoints/functions.resource-usage?interval=${interval}&function_id=${functionId}`,
|
||||
{
|
||||
signal,
|
||||
}
|
||||
)
|
||||
if (response.error) {
|
||||
throw response.error
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
export type FunctionsResourceUsageData = Awaited<ReturnType<typeof getFunctionsResourceUsage>>
|
||||
export type FunctionsResourceUsageError = unknown
|
||||
|
||||
export const useFunctionsResourceUsageQuery = <TData = FunctionsResourceUsageData>(
|
||||
{ projectRef, functionId, interval }: FunctionsResourceUsageVariables,
|
||||
{
|
||||
enabled = true,
|
||||
...options
|
||||
}: UseQueryOptions<FunctionsResourceUsageData, FunctionsResourceUsageError, TData> = {}
|
||||
) =>
|
||||
useQuery<FunctionsResourceUsageData, FunctionsResourceUsageError, TData>(
|
||||
analyticsKeys.functionsResourceUsage(projectRef, { functionId, interval }),
|
||||
({ signal }) => getFunctionsResourceUsage({ projectRef, functionId, interval }, signal),
|
||||
{
|
||||
enabled:
|
||||
enabled &&
|
||||
typeof projectRef !== 'undefined' &&
|
||||
typeof functionId !== 'undefined' &&
|
||||
typeof interval !== 'undefined',
|
||||
...options,
|
||||
}
|
||||
)
|
||||
@@ -1,8 +1,62 @@
|
||||
export const analyticsKeys = {
|
||||
functionsInvStats: (
|
||||
projectRef: string | undefined,
|
||||
{ interval, functionId }: { functionId: string | undefined; interval: string | undefined }
|
||||
) => ['projects', projectRef, 'functions-inv-stats', { interval, functionId }] as const,
|
||||
{
|
||||
interval,
|
||||
functionId,
|
||||
}: {
|
||||
functionId: string | undefined
|
||||
interval: string | undefined
|
||||
}
|
||||
) =>
|
||||
[
|
||||
'projects',
|
||||
projectRef,
|
||||
'functions-inv-stats',
|
||||
{
|
||||
interval,
|
||||
functionId,
|
||||
},
|
||||
] as const,
|
||||
functionsReqStats: (
|
||||
projectRef: string | undefined,
|
||||
{
|
||||
interval,
|
||||
functionId,
|
||||
}: {
|
||||
functionId: string | undefined
|
||||
interval: string | undefined
|
||||
}
|
||||
) =>
|
||||
[
|
||||
'projects',
|
||||
projectRef,
|
||||
'functions-req-stats',
|
||||
{
|
||||
interval,
|
||||
functionId,
|
||||
},
|
||||
] as const,
|
||||
functionsResourceUsage: (
|
||||
projectRef: string | undefined,
|
||||
{
|
||||
interval,
|
||||
functionId,
|
||||
}: {
|
||||
functionId: string | undefined
|
||||
interval: string | undefined
|
||||
}
|
||||
) =>
|
||||
[
|
||||
'projects',
|
||||
projectRef,
|
||||
'functions-resource-usage',
|
||||
{
|
||||
interval,
|
||||
functionId,
|
||||
},
|
||||
] as const,
|
||||
|
||||
dailyStats: (
|
||||
projectRef: string | undefined,
|
||||
{
|
||||
@@ -10,7 +64,12 @@ export const analyticsKeys = {
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
}: { attribute?: string; startDate?: string; endDate?: string; interval?: string }
|
||||
}: {
|
||||
attribute?: string
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
interval?: string
|
||||
}
|
||||
) =>
|
||||
[
|
||||
'projects',
|
||||
@@ -58,7 +117,12 @@ export const analyticsKeys = {
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
}: { attribute?: string; startDate?: string; endDate?: string; interval?: string }
|
||||
}: {
|
||||
attribute?: string
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
interval?: string
|
||||
}
|
||||
) =>
|
||||
[
|
||||
'projects',
|
||||
|
||||
@@ -5,11 +5,14 @@ import { isUnixMicro, unixMicroToIsoTimestamp } from 'components/interfaces/Sett
|
||||
import FunctionsLayout from 'components/layouts/FunctionsLayout'
|
||||
import AreaChart from 'components/ui/Charts/AreaChart'
|
||||
import BarChart from 'components/ui/Charts/BarChart'
|
||||
import StackedBarChart from 'components/ui/Charts/StackedBarChart'
|
||||
import NoPermission from 'components/ui/NoPermission'
|
||||
import { useFunctionsInvStatsQuery } from 'data/analytics/functions-inv-stats-query'
|
||||
import { useFunctionsReqStatsQuery } from 'data/analytics/functions-req-stats-query'
|
||||
import { useFunctionsResourceUsageQuery } from 'data/analytics/functions-resource-usage-query'
|
||||
import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query'
|
||||
import dayjs, { Dayjs } from 'dayjs'
|
||||
import { useCheckPermissions } from 'hooks'
|
||||
import { useCheckPermissions, useFlag } from 'hooks'
|
||||
import useFillTimeseriesSorted from 'hooks/analytics/useFillTimeseriesSorted'
|
||||
import sumBy from 'lodash/sumBy'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
@@ -26,10 +29,34 @@ const CHART_INTERVALS: ChartIntervals[] = [
|
||||
startUnit: 'minute',
|
||||
format: 'MMM D, h:mm:ssa',
|
||||
},
|
||||
{ key: '15min', label: '15 min', startValue: 15, startUnit: 'minute', format: 'MMM D, h:mma' },
|
||||
{ key: '1hr', label: '1 hour', startValue: 1, startUnit: 'hour', format: 'MMM D, h:mma' },
|
||||
{ key: '1day', label: '1 day', startValue: 1, startUnit: 'hour', format: 'MMM D, h:mma' },
|
||||
{ key: '7day', label: '7 days', startValue: 7, startUnit: 'day', format: 'MMM D' },
|
||||
{
|
||||
key: '15min',
|
||||
label: '15 min',
|
||||
startValue: 15,
|
||||
startUnit: 'minute',
|
||||
format: 'MMM D, h:mma',
|
||||
},
|
||||
{
|
||||
key: '1hr',
|
||||
label: '1 hour',
|
||||
startValue: 1,
|
||||
startUnit: 'hour',
|
||||
format: 'MMM D, h:mma',
|
||||
},
|
||||
{
|
||||
key: '1day',
|
||||
label: '1 day',
|
||||
startValue: 1,
|
||||
startUnit: 'hour',
|
||||
format: 'MMM D, h:mma',
|
||||
},
|
||||
{
|
||||
key: '7day',
|
||||
label: '7 days',
|
||||
startValue: 7,
|
||||
startUnit: 'day',
|
||||
format: 'MMM D',
|
||||
},
|
||||
]
|
||||
|
||||
const PageLayout: NextPageWithLayout = () => {
|
||||
@@ -37,21 +64,50 @@ const PageLayout: NextPageWithLayout = () => {
|
||||
const { ref: projectRef, functionSlug } = useParams()
|
||||
const [interval, setInterval] = useState<string>('15min')
|
||||
const selectedInterval = CHART_INTERVALS.find((i) => i.key === interval) || CHART_INTERVALS[1]
|
||||
const { data: selectedFunction } = useEdgeFunctionQuery({ projectRef, slug: functionSlug })
|
||||
const id = selectedFunction?.id
|
||||
|
||||
const { data, error } = useFunctionsInvStatsQuery({
|
||||
const { data: selectedFunction } = useEdgeFunctionQuery({
|
||||
projectRef,
|
||||
functionId: id,
|
||||
interval: selectedInterval.key,
|
||||
slug: functionSlug,
|
||||
})
|
||||
const isChartLoading = !data?.result && !error ? true : false
|
||||
const normalizedData = useMemo(() => {
|
||||
return (data?.result || []).map((d: any) => ({
|
||||
...d,
|
||||
timestamp: isUnixMicro(d.timestamp) ? unixMicroToIsoTimestamp(d.timestamp) : d.timestamp,
|
||||
}))
|
||||
}, [data?.result])
|
||||
const id = selectedFunction?.id
|
||||
const resourceUsageMetricsEnabled = useFlag('enableResourceUsageMetricsForEdgeFunctions')
|
||||
|
||||
const invStatsResult = useFunctionsInvStatsQuery(
|
||||
{
|
||||
projectRef,
|
||||
functionId: id,
|
||||
interval: selectedInterval.key,
|
||||
},
|
||||
{ enabled: !resourceUsageMetricsEnabled }
|
||||
)
|
||||
|
||||
const reqStatsResult = useFunctionsReqStatsQuery(
|
||||
{
|
||||
projectRef,
|
||||
functionId: id,
|
||||
interval: selectedInterval.key,
|
||||
},
|
||||
{ enabled: resourceUsageMetricsEnabled }
|
||||
)
|
||||
|
||||
const resourceUsageResult = useFunctionsResourceUsageQuery(
|
||||
{
|
||||
projectRef,
|
||||
functionId: id,
|
||||
interval: selectedInterval.key,
|
||||
},
|
||||
{ enabled: resourceUsageMetricsEnabled }
|
||||
)
|
||||
|
||||
const reqStatsData = useMemo(() => {
|
||||
const result = resourceUsageMetricsEnabled
|
||||
? reqStatsResult.data?.result
|
||||
: invStatsResult.data?.result
|
||||
return result || []
|
||||
}, [invStatsResult.data, reqStatsResult.data, resourceUsageMetricsEnabled])
|
||||
|
||||
const resourceUsageData = useMemo(() => {
|
||||
return resourceUsageResult.data?.result || []
|
||||
}, [resourceUsageResult.data])
|
||||
|
||||
const [startDate, endDate]: [Dayjs, Dayjs] = useMemo(() => {
|
||||
const start = dayjs()
|
||||
@@ -61,10 +117,29 @@ const PageLayout: NextPageWithLayout = () => {
|
||||
const end = dayjs().startOf(selectedInterval.startUnit as dayjs.ManipulateType)
|
||||
return [start, end]
|
||||
}, [selectedInterval])
|
||||
const chartData = useFillTimeseriesSorted(
|
||||
normalizedData,
|
||||
|
||||
const execTimeChartData = useFillTimeseriesSorted(
|
||||
reqStatsData,
|
||||
'timestamp',
|
||||
['avg_execution_time', 'count'],
|
||||
['avg_execution_time'],
|
||||
0,
|
||||
startDate.toISOString(),
|
||||
endDate.toISOString()
|
||||
)
|
||||
|
||||
const invocationsChartData = useFillTimeseriesSorted(
|
||||
reqStatsData,
|
||||
'timestamp',
|
||||
['count', 'success_count', 'redirect_count', 'client_err_count', 'server_err_count'],
|
||||
0,
|
||||
startDate.toISOString(),
|
||||
endDate.toISOString()
|
||||
)
|
||||
|
||||
const resourceUsageChartData = useFillTimeseriesSorted(
|
||||
resourceUsageData,
|
||||
'timestamp',
|
||||
['avg_cpu_time_used', 'avg_memory_used'],
|
||||
0,
|
||||
startDate.toISOString(),
|
||||
endDate.toISOString()
|
||||
@@ -114,10 +189,12 @@ const PageLayout: NextPageWithLayout = () => {
|
||||
<ReportWidget
|
||||
title="Execution time"
|
||||
tooltip="Average execution time of function invocations"
|
||||
data={chartData}
|
||||
isLoading={isChartLoading}
|
||||
data={execTimeChartData}
|
||||
isLoading={
|
||||
resourceUsageMetricsEnabled ? reqStatsResult.isLoading : invStatsResult.isLoading
|
||||
}
|
||||
renderer={(props) => {
|
||||
const latest = normalizedData[normalizedData.length - 1]
|
||||
const latest = props.data[props.data.length - 1]
|
||||
let highlightedValue
|
||||
if (latest) {
|
||||
highlightedValue = latest['avg_execution_time']
|
||||
@@ -135,28 +212,132 @@ const PageLayout: NextPageWithLayout = () => {
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<ReportWidget
|
||||
title="Invocations"
|
||||
data={chartData}
|
||||
isLoading={isChartLoading}
|
||||
renderer={(props) => (
|
||||
<BarChart
|
||||
className="w-full"
|
||||
xAxisKey="timestamp"
|
||||
yAxisKey="count"
|
||||
data={props.data}
|
||||
highlightedValue={sumBy(props.data, 'count')}
|
||||
customDateFormat={selectedInterval.format}
|
||||
onBarClick={(v) => {
|
||||
router.push(
|
||||
`/project/${projectRef}/functions/${functionSlug}/invocations?its=${startDate.toISOString()}&ite=${
|
||||
v.timestamp
|
||||
}`
|
||||
{resourceUsageMetricsEnabled ? (
|
||||
<ReportWidget
|
||||
title="Invocations"
|
||||
data={invocationsChartData}
|
||||
isLoading={reqStatsResult.isLoading}
|
||||
renderer={(props) => {
|
||||
const data = props.data
|
||||
.map((d: any) => [
|
||||
{
|
||||
status: '2xx',
|
||||
count: d.success_count,
|
||||
timestamp: d.timestamp,
|
||||
},
|
||||
{
|
||||
status: '3xx',
|
||||
count: d.redirect_count,
|
||||
timestamp: d.timestamp,
|
||||
},
|
||||
{
|
||||
status: '4xx',
|
||||
count: d.client_err_count,
|
||||
timestamp: d.timestamp,
|
||||
},
|
||||
{
|
||||
status: '5xx',
|
||||
count: d.server_err_count,
|
||||
timestamp: d.timestamp,
|
||||
},
|
||||
])
|
||||
.flat()
|
||||
|
||||
return (
|
||||
<StackedBarChart
|
||||
className="w-full"
|
||||
xAxisKey="timestamp"
|
||||
yAxisKey="count"
|
||||
stackKey="status"
|
||||
data={data}
|
||||
highlightedValue={sumBy(data, 'count')}
|
||||
customDateFormat={selectedInterval.format}
|
||||
stackColors={['brand', 'slate', 'yellow', 'red']}
|
||||
onBarClick={() => {
|
||||
router.push(
|
||||
`/project/${projectRef}/functions/${functionSlug}/invocations?its=${startDate.toISOString()}`
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ReportWidget
|
||||
title="Invocations"
|
||||
data={invocationsChartData}
|
||||
isLoading={invStatsResult.isLoading}
|
||||
renderer={(props) => (
|
||||
<BarChart
|
||||
className="w-full"
|
||||
xAxisKey="timestamp"
|
||||
yAxisKey="count"
|
||||
data={props.data}
|
||||
highlightedValue={sumBy(props.data, 'count')}
|
||||
customDateFormat={selectedInterval.format}
|
||||
onBarClick={(v) => {
|
||||
router.push(
|
||||
`/project/${projectRef}/functions/${functionSlug}/invocations?its=${startDate.toISOString()}&ite=${
|
||||
v.timestamp
|
||||
}`
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{resourceUsageMetricsEnabled && (
|
||||
<>
|
||||
<ReportWidget
|
||||
title="CPU time"
|
||||
tooltip="Average CPU time usage for the function"
|
||||
data={resourceUsageChartData}
|
||||
isLoading={resourceUsageResult.isLoading}
|
||||
renderer={(props) => {
|
||||
const latest = props.data[props.data.length - 1]
|
||||
let highlightedValue
|
||||
if (latest) {
|
||||
highlightedValue = latest['avg_cpu_time_used']
|
||||
}
|
||||
return (
|
||||
<AreaChart
|
||||
className="w-full"
|
||||
xAxisKey="timestamp"
|
||||
customDateFormat={selectedInterval.format}
|
||||
yAxisKey="avg_cpu_time_used"
|
||||
data={props.data}
|
||||
format="ms"
|
||||
highlightedValue={highlightedValue}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<ReportWidget
|
||||
title="Memory"
|
||||
tooltip="Average memory usage for the function"
|
||||
data={resourceUsageChartData}
|
||||
isLoading={resourceUsageResult.isLoading}
|
||||
renderer={(props) => {
|
||||
const latest = props.data[props.data.length - 1]
|
||||
let highlightedValue
|
||||
if (latest) {
|
||||
highlightedValue = latest['avg_memory_used']
|
||||
}
|
||||
return (
|
||||
<AreaChart
|
||||
className="w-full"
|
||||
xAxisKey="timestamp"
|
||||
customDateFormat={selectedInterval.format}
|
||||
yAxisKey="avg_memory_used"
|
||||
data={props.data}
|
||||
format="MB"
|
||||
highlightedValue={highlightedValue}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user