1- import { useMemo , useState } from "react" ;
1+ import { useState } from "react" ;
22import { useUserTimeZone } from "~/components/primitives/DateTime" ;
33import { DetailChartBlock } from "~/components/primitives/DetailChartBlock" ;
44import { TimeFilter } from "~/components/runs/v3/SharedFilters" ;
@@ -42,6 +42,12 @@ import {
4242 assignGroupColors ,
4343 thresholdChartLegend ,
4444} from "./AlertThresholdChart" ;
45+ import {
46+ hasTileData ,
47+ isLimitComponent ,
48+ isLimitsComposite ,
49+ type LimitTileSeries ,
50+ } from "./limitTiles" ;
4551import { defaultGroupSelection , MAX_VISIBLE_GROUPS } from "./queryGroupSelection" ;
4652
4753const METRIC_LABEL : Record < string , string > = {
@@ -90,33 +96,68 @@ type AlertDetailChartProps = {
9096 metricSeriesMap : Map < string , MetricSeriesPoint [ ] > ;
9197 healthBands : HealthBandRow [ ] ;
9298 componentSeries ?: HealthQuerySeries [ ] ;
99+ limitTiles ?: LimitTileSeries [ ] ;
93100 window : DetailWindow & { isLifetime ?: boolean ; explicitTo ?: boolean } ;
94101 now : Date ;
95102 createdAt ?: Date ;
96103 queryGroups ?: string [ ] ;
97104 querySeriesMode ?: "threshold" | "absence" | "match" ;
98105} ;
99106
100- export function AlertDetailChart ( props : AlertDetailChartProps ) {
107+ function limitsAsQueryProps (
108+ props : AlertDetailChartProps ,
109+ tiles : LimitTileSeries [ ]
110+ ) : AlertDetailChartProps {
111+ const { alert } = props ;
112+ const single = tiles . length === 1 ? tiles [ 0 ] : undefined ;
113+ return {
114+ ...props ,
115+ alert : {
116+ ...alert ,
117+ kind : "threshold" ,
118+ operator : ">" ,
119+ thresholdValue : tiles . find ( ( t ) => t . threshold !== null ) ?. threshold ?? undefined ,
120+ unitSuffix : "%" ,
121+ scope : { type : "all" } ,
122+ currentReading : { status : "unavailable" } ,
123+ capabilityTitle : single ?. label ?? alert . capabilityTitle ,
124+ } as AlertDetail ,
125+ metricSeriesMap : single
126+ ? new Map ( [ [ "__agg__" , single . points ] ] )
127+ : new Map ( tiles . map ( ( t ) => [ t . label , t . points ] ) ) ,
128+ queryGroups : tiles . length > 1 ? tiles . map ( ( t ) => t . label ) : [ ] ,
129+ querySeriesMode : "threshold" ,
130+ } ;
131+ }
132+
133+ export function AlertDetailChart ( rawProps : AlertDetailChartProps ) {
101134 const [ healthView , setHealthView ] = useState < HealthView > ( "together" ) ;
102135 const [ picked , setPicked ] = useState < string [ ] | null > ( null ) ;
136+ const isLimits =
137+ rawProps . alert . kind === "state" &&
138+ ( isLimitComponent ( rawProps . alert . capabilityKey ) ||
139+ isLimitsComposite ( rawProps . alert . capabilityKey ) ) ;
140+ const props = isLimits
141+ ? limitsAsQueryProps (
142+ rawProps ,
143+ ( rawProps . limitTiles ?? [ ] ) . filter ( ( t ) => ! t . failed && hasTileData ( t . points ) )
144+ )
145+ : rawProps ;
103146 const { alert, window, metricSeriesMap, queryGroups = [ ] } = props ;
104147 const showToggle = alert . kind === "state" && ( props . componentSeries ?. length ?? 0 ) > 1 ;
148+ const groups = queryGroups ;
149+ const groupSeries = metricSeriesMap ;
105150 const grouped =
106151 alert . kind === "threshold" && props . querySeriesMode !== "match" && queryGroups . length > 0 ;
107- const defaults = useMemo (
108- ( ) =>
109- grouped
110- ? defaultGroupSelection ( {
111- groups : queryGroups ,
112- seriesMap : metricSeriesMap ,
113- operator : alert . operator ,
114- range : { since : window . since . getTime ( ) , until : window . until . getTime ( ) } ,
115- } )
116- : [ ] ,
117- [ grouped , queryGroups , metricSeriesMap , alert . operator , window . since , window . until ]
118- ) ;
119- const showPicker = grouped && queryGroups . length > 1 ;
152+ const defaults = grouped
153+ ? defaultGroupSelection ( {
154+ groups,
155+ seriesMap : groupSeries ,
156+ operator : alert . operator ,
157+ range : { since : window . since . getTime ( ) , until : window . until . getTime ( ) } ,
158+ } )
159+ : [ ] ;
160+ const showPicker = grouped && groups . length > 1 ;
120161 const selected = showPicker ? ( picked ?? defaults ) : defaults ;
121162 // Colours carry over from the previous selection, so a toggle doesn't repaint other groups.
122163 const [ colorState , setColorState ] = useState ( { key : "" , colors : new Map < string , string > ( ) } ) ;
@@ -135,29 +176,42 @@ export function AlertDetailChart(props: AlertDetailChartProps) {
135176 : [ ...selected , group ]
136177 ) ;
137178
179+ const loadedTiles = rawProps . limitTiles ?? [ ] ;
180+ const limitsUnloaded =
181+ isLimits &&
182+ metricSeriesMap . size === 0 &&
183+ ( loadedTiles . length === 0 || loadedTiles . some ( ( t ) => t . failed ) ) ;
184+
138185 return (
139186 < DetailChartBlock
140187 toolbar = { < AlertDetailFilterBar lifetime = { window . isLifetime ? window : undefined } /> }
141188 >
142189 < div className = "min-h-[210px] flex-1" >
143- < AlertDetailChartBody
144- { ...props }
145- healthView = { healthView }
146- selectedGroups = { grouped ? selected : undefined }
147- groupColors = { groupColors }
148- accessory = {
149- showToggle ? (
150- < HealthViewToggle value = { healthView } onChange = { setHealthView } />
151- ) : showPicker ? (
152- < GroupsDropdown
153- groups = { queryGroups }
154- selected = { selected }
155- onToggle = { toggleGroup }
156- colors = { groupColors }
157- />
158- ) : undefined
159- }
160- />
190+ { limitsUnloaded ? (
191+ < div className = "flex h-full items-center justify-center text-xs text-text-dimmed" >
192+ Couldn't load this data. Try again in a moment.
193+ </ div >
194+ ) : (
195+ < AlertDetailChartBody
196+ { ...props }
197+ healthView = { healthView }
198+ selectedGroups = { grouped ? selected : undefined }
199+ groupColors = { groupColors }
200+ accessory = {
201+ showToggle ? (
202+ < HealthViewToggle value = { healthView } onChange = { setHealthView } />
203+ ) : showPicker ? (
204+ < GroupsDropdown
205+ groups = { groups }
206+ selected = { selected }
207+ onToggle = { toggleGroup }
208+ colors = { groupColors }
209+ noun = { isLimits ? "limits" : undefined }
210+ />
211+ ) : undefined
212+ }
213+ />
214+ ) }
161215 </ div >
162216 </ DetailChartBlock >
163217 ) ;
@@ -168,6 +222,7 @@ function AlertDetailChartBody({
168222 metricSeriesMap,
169223 healthBands,
170224 componentSeries = [ ] ,
225+ limitTiles = [ ] ,
171226 window,
172227 now,
173228 createdAt,
@@ -234,7 +289,9 @@ function AlertDetailChartBody({
234289 : undefined ;
235290 const threshold =
236291 typeof facts ?. threshold === "number" ? facts . threshold : ( alert . thresholdValue ?? null ) ;
237- const label = METRIC_LABEL [ alert . capabilityKey ] ?? alert . capabilityTitle ;
292+ const soleGroup =
293+ pick . multi && pick . seriesMap . size === 1 ? [ ...pick . seriesMap . keys ( ) ] [ 0 ] : undefined ;
294+ const label = soleGroup ?? METRIC_LABEL [ alert . capabilityKey ] ?? alert . capabilityTitle ;
238295 const chart = pick . multi ? (
239296 < AlertMultiSeriesChart
240297 seriesMap = { pick . seriesMap }
0 commit comments