@@ -14,6 +14,17 @@ import {
1414} from '../insights/InsightQueryChart' ;
1515import { Card , CardContent , CardHeader , CardTitle } from '../ui/card' ;
1616import { Tabs , TabsContent , TabsList , TabsTrigger } from '../ui/tabs' ;
17+ import { TimeEventChart } from '../chart/TimeEventChart' ;
18+ import { LoadingView } from '../LoadingView' ;
19+ import { DateUnit , getDateArray } from '@tianji/shared' ;
20+ import type { InsightsRawData } from '@/hooks/useInsightsData' ;
21+ import type { ChartConfig } from '../ui/chart' ;
22+
23+ // Status color tokens used in the Errors tab.
24+ // Keep the success/failure colors aligned with the rest of the app
25+ // (see AIGatewayStatus: bg-green-* for Success, bg-red-* for Failed).
26+ const STATUS_SUCCESS_COLOR = '#22c55e' ;
27+ const STATUS_FAILED_COLOR = '#ef4444' ;
1728
1829interface AIGatewayAnalyticsProps {
1930 gatewayId : string ;
@@ -48,6 +59,22 @@ export const AIGatewayAnalytics: React.FC<AIGatewayAnalyticsProps> = React.memo(
4859 [ startDate , endDate , unit ]
4960 ) ;
5061
62+ // Force Success vs Failure colors. Series keys follow the
63+ // `${alias|name}-${groupValue}` pattern from generateSeriesName.
64+ const statusChartConfig : ChartConfig = useMemo (
65+ ( ) => ( {
66+ '$all_event-Success' : {
67+ label : t ( 'Success' ) ,
68+ color : STATUS_SUCCESS_COLOR ,
69+ } ,
70+ '$all_event-Failed' : {
71+ label : t ( 'Failed' ) ,
72+ color : STATUS_FAILED_COLOR ,
73+ } ,
74+ } ) ,
75+ [ t ]
76+ ) ;
77+
5178 return (
5279 < div className = "space-y-6" >
5380 { /* Header */ }
@@ -509,6 +536,7 @@ export const AIGatewayAnalytics: React.FC<AIGatewayAnalyticsProps> = React.memo(
509536 groups = { [ { value : 'status' , type : 'string' } ] }
510537 time = { timeConfig }
511538 chartType = "pie"
539+ chartConfig = { statusChartConfig }
512540 valueProcessor = { defaultValueProcessor . alwaysPositive }
513541 />
514542 </ CardContent >
@@ -545,33 +573,22 @@ export const AIGatewayAnalytics: React.FC<AIGatewayAnalyticsProps> = React.memo(
545573 </ Card >
546574 </ div >
547575
548- { /* Success Rate Over Time */ }
576+ { /* Error Rate Over Time */ }
549577 < Card >
550578 < CardHeader >
551- < CardTitle > { t ( 'Successful Requests Trend' ) } </ CardTitle >
579+ < CardTitle > { t ( 'Error Rate Trend' ) } </ CardTitle >
552580 < p className = "text-muted-foreground text-sm" >
553- { t ( 'Number of events with Success status over time' ) }
581+ { t (
582+ 'Percentage of failed requests out of total requests over time (Failed / Total %)'
583+ ) }
554584 </ p >
555585 </ CardHeader >
556586 < CardContent >
557- < InsightQueryChart
587+ < ErrorRateChart
558588 className = "h-[300px] w-full"
559589 workspaceId = { workspaceId }
560- insightId = { gatewayId }
561- insightType = "aigateway"
562- metrics = { [ { name : '$all_event' , math : 'events' } ] }
563- filters = { [
564- {
565- name : 'status' ,
566- type : 'string' ,
567- operator : 'equals' ,
568- value : 'Success' ,
569- } ,
570- ] }
571- groups = { [ ] }
590+ gatewayId = { gatewayId }
572591 time = { timeConfig }
573- chartType = "area"
574- valueProcessor = { defaultValueProcessor . alwaysPositive }
575592 />
576593 </ CardContent >
577594 </ Card >
@@ -615,3 +632,100 @@ export const AIGatewayAnalytics: React.FC<AIGatewayAnalyticsProps> = React.memo(
615632 }
616633) ;
617634AIGatewayAnalytics . displayName = 'AIGatewayAnalytics' ;
635+
636+ interface ErrorRateChartProps {
637+ workspaceId : string ;
638+ gatewayId : string ;
639+ time : {
640+ startAt : number ;
641+ endAt : number ;
642+ unit : DateUnit ;
643+ timezone ?: string ;
644+ } ;
645+ className ?: string ;
646+ }
647+
648+ const ErrorRateChart : React . FC < ErrorRateChartProps > = React . memo ( ( props ) => {
649+ const { workspaceId, gatewayId, time, className } = props ;
650+ const { t } = useTranslation ( ) ;
651+
652+ const { data : rawData = [ ] , isLoading } = trpc . insights . query . useQuery (
653+ {
654+ workspaceId,
655+ insightId : gatewayId ,
656+ insightType : 'aigateway' ,
657+ metrics : [ { name : '$all_event' , math : 'events' } ] ,
658+ filters : [ ] ,
659+ groups : [ { value : 'status' , type : 'string' } ] ,
660+ time : {
661+ timezone : getUserTimezone ( ) ,
662+ ...time ,
663+ } ,
664+ } ,
665+ {
666+ trpc : {
667+ context : {
668+ skipBatch : true ,
669+ } ,
670+ } ,
671+ }
672+ ) ;
673+
674+ const chartData = useMemo ( ( ) => {
675+ const dataArr = rawData as InsightsRawData [ ] ;
676+ // Aggregate success/failed counts per date bucket
677+ const dateMap = new Map < string , { success : number ; failed : number } > ( ) ;
678+
679+ dataArr . forEach ( ( item ) => {
680+ const status = ( item as any ) . status ;
681+ item . data . forEach ( ( d ) => {
682+ const entry = dateMap . get ( d . date ) ?? { success : 0 , failed : 0 } ;
683+ if ( status === 'Success' ) {
684+ entry . success += d . value ;
685+ } else if ( status === 'Failed' ) {
686+ entry . failed += d . value ;
687+ }
688+ dateMap . set ( d . date , entry ) ;
689+ } ) ;
690+ } ) ;
691+
692+ const arr = Array . from ( dateMap . entries ( ) ) . map (
693+ ( [ date , { success, failed } ] ) => {
694+ const total = success + failed ;
695+ const rate = total > 0 ? ( failed / total ) * 100 : 0 ;
696+ return { date, value : Math . max ( 0 , Math . min ( 100 , rate ) ) } ;
697+ }
698+ ) ;
699+
700+ if ( arr . length === 0 ) {
701+ return [ ] ;
702+ }
703+
704+ return getDateArray ( arr , time . startAt , time . endAt , time . unit ) ;
705+ } , [ rawData , time . startAt , time . endAt , time . unit ] ) ;
706+
707+ const chartConfig : ChartConfig = useMemo (
708+ ( ) => ( {
709+ value : {
710+ label : t ( 'Error Rate' ) ,
711+ color : STATUS_FAILED_COLOR ,
712+ } ,
713+ } ) ,
714+ [ t ]
715+ ) ;
716+
717+ return (
718+ < LoadingView isLoading = { isLoading } >
719+ < TimeEventChart
720+ className = { className }
721+ data = { chartData }
722+ unit = { time . unit }
723+ chartConfig = { chartConfig }
724+ chartType = "area"
725+ yAxisDomain = { [ 0 , 100 ] }
726+ valueFormatter = { ( v ) => `${ v . toFixed ( 2 ) } %` }
727+ />
728+ </ LoadingView >
729+ ) ;
730+ } ) ;
731+ ErrorRateChart . displayName = 'ErrorRateChart' ;
0 commit comments