11'use client'
22
33import { useMemo } from 'react'
4- import { BarChart , ChartFrame , DashboardMetric , DonutChart , formatChartLatency } from '@sim/emcn'
4+ import {
5+ BarChart ,
6+ type BarChartSeries ,
7+ ChartFrame ,
8+ ChartLegend ,
9+ type ChartLegendItem ,
10+ DashboardMetric ,
11+ formatChartLatency ,
12+ } from '@sim/emcn'
513import type { OrganizationActivitySummary } from '@/lib/api/contracts/organization-activity'
614import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section'
15+ import { USAGE_CHAT_COLOR } from '@/ee/organization-usage/constants'
16+ import { useLegendHighlight } from '@/ee/organization-usage/hooks/use-legend-highlight'
717import { useOrganizationActivitySummary } from '@/hooks/queries/organization-activity'
818import type { OrganizationUsageWindowKey } from '@/hooks/queries/utils/organization-usage-keys'
919
20+ const CHART_HEIGHT = 180
21+
22+ /**
23+ * Outcome layers, bottom-up. Failed is the status red and sits on the stack where a
24+ * spike reads at a glance; Other (cancelled, paused, unfinished) stays neutral.
25+ */
26+ const OUTCOMES = [
27+ { id : 'completed' , label : 'Completed' , color : 'var(--brand-blue)' } ,
28+ { id : 'failed' , label : 'Failed' , color : 'var(--text-error)' } ,
29+ { id : 'other' , label : 'Other' , color : 'var(--text-muted)' } ,
30+ ] as const
31+
32+ const OUTCOME_LEGEND : ChartLegendItem [ ] = [ ...OUTCOMES ]
33+ const OUTCOME_IDS = OUTCOMES . map ( ( outcome ) => outcome . id )
34+
35+ type ActivityPoint = OrganizationActivitySummary [ 'series' ] [ number ]
36+
37+ const OUTCOME_VALUE : Record < ( typeof OUTCOMES ) [ number ] [ 'id' ] , ( point : ActivityPoint ) => number > = {
38+ completed : ( point ) => point . completed ,
39+ failed : ( point ) => point . failed ,
40+ other : ( point ) => Math . max ( 0 , point . workflowRuns - point . completed - point . failed ) ,
41+ }
42+
1043interface ActivitySummaryProps {
1144 summary ?: OrganizationActivitySummary
1245 loading ?: boolean
@@ -24,30 +57,26 @@ export function formatFailureRate(rate: number | null): string {
2457}
2558
2659export function ActivitySummary ( { summary, loading, error, onRetry } : ActivitySummaryProps ) {
27- const workflowSeries = useMemo (
60+ const highlight = useLegendHighlight ( OUTCOME_IDS )
61+
62+ const outcomeSeries = useMemo < BarChartSeries [ ] > (
2863 ( ) =>
29- summary ?. series . map ( ( point ) => ( {
30- timestamp : point . timestamp ,
31- value : point . workflowRuns ,
32- } ) ) ?? [ ] ,
64+ OUTCOMES . map ( ( outcome ) => ( {
65+ ...outcome ,
66+ data : ( summary ?. series ?? [ ] ) . map ( ( point ) => ( {
67+ timestamp : point . timestamp ,
68+ value : OUTCOME_VALUE [ outcome . id ] ( point ) ,
69+ } ) ) ,
70+ } ) ) ,
3371 [ summary ?. series ]
3472 )
73+
3574 const chatSeries = useMemo (
3675 ( ) =>
37- summary ?. series . map ( ( point ) => ( {
38- timestamp : point . timestamp ,
39- value : point . chatRuns ,
40- } ) ) ?? [ ] ,
41- [ summary ?. series ]
42- )
43- const failureSeries = useMemo (
44- ( ) =>
45- summary ?. series . map ( ( point ) => ( {
46- timestamp : point . timestamp ,
47- value : point . failed ,
48- } ) ) ?? [ ] ,
76+ summary ?. series . map ( ( point ) => ( { timestamp : point . timestamp , value : point . chatRuns } ) ) ?? [ ] ,
4977 [ summary ?. series ]
5078 )
79+
5180 const totals = summary ?. totals
5281 const metrics = [
5382 {
@@ -81,16 +110,8 @@ export function ActivitySummary({ summary, loading, error, onRetry }: ActivitySu
81110 description : 'Completed and failed workflows with a recorded duration.' ,
82111 } ,
83112 ]
84- const outcomes = [
85- { label : 'Completed' , value : totals ?. completed ?? 0 , color : 'var(--indicator-seat-filled)' } ,
86- { label : 'Failed' , value : totals ?. failed ?? 0 , color : 'var(--text-error)' } ,
87- {
88- label : 'Other' ,
89- value : totals ? totals . workflowRuns - totals . completed - totals . failed : 0 ,
90- color : 'var(--text-muted)' ,
91- } ,
92- ]
93113 const chartState = { loading, error : error ? "Couldn't load activity." : undefined , onRetry }
114+
94115 return (
95116 < div className = 'flex flex-col gap-5' >
96117 < div className = 'grid grid-cols-[repeat(auto-fit,minmax(min(120px,100%),1fr))] gap-4' >
@@ -104,41 +125,32 @@ export function ActivitySummary({ summary, loading, error, onRetry }: ActivitySu
104125 ) ) }
105126 </ div >
106127 < div className = 'grid grid-cols-[repeat(auto-fit,minmax(min(280px,100%),1fr))] gap-6' >
107- < ChartFrame title = 'Workflow runs' height = { 160 } { ...chartState } >
128+ < div className = 'flex min-w-0 flex-col gap-2' >
129+ < ChartFrame
130+ title = 'Workflow runs'
131+ description = 'Other includes cancelled, paused, and unfinished runs.'
132+ height = { CHART_HEIGHT }
133+ { ...chartState }
134+ >
135+ < BarChart
136+ label = ''
137+ xAxisFormat = 'date'
138+ height = { CHART_HEIGHT }
139+ series = { outcomeSeries }
140+ highlightedSeriesId = { highlight . highlightedId }
141+ />
142+ </ ChartFrame >
143+ < ChartLegend layout = 'row' items = { OUTCOME_LEGEND } { ...highlight . legendProps } />
144+ </ div >
145+ < ChartFrame title = 'Chat runs' height = { CHART_HEIGHT } { ...chartState } >
108146 < BarChart
109- xAxisFormat = 'date'
110- data = { workflowSeries }
111147 label = ''
112- color = 'var(--indicator-seat-filled)'
113- height = { 160 }
114- />
115- </ ChartFrame >
116- < ChartFrame title = 'Chat runs' height = { 160 } { ...chartState } >
117- < BarChart
118148 xAxisFormat = 'date'
149+ height = { CHART_HEIGHT }
119150 data = { chatSeries }
120- label = ''
121- color = 'var(--indicator-seat-filled)'
122- height = { 160 }
151+ color = { USAGE_CHAT_COLOR }
123152 />
124153 </ ChartFrame >
125- < ChartFrame title = 'Failed runs' height = { 160 } { ...chartState } >
126- < BarChart
127- xAxisFormat = 'date'
128- data = { failureSeries }
129- label = ''
130- color = 'var(--text-error)'
131- height = { 160 }
132- />
133- </ ChartFrame >
134- < ChartFrame
135- title = 'Workflow outcomes'
136- description = 'Other includes cancelled, paused, and unfinished runs.'
137- height = { 160 }
138- { ...chartState }
139- >
140- < DonutChart segments = { outcomes } label = 'Workflow outcomes' />
141- </ ChartFrame >
142154 </ div >
143155 </ div >
144156 )
0 commit comments