@@ -105,6 +105,130 @@ export async function getLogsBatch(project, runs) {
105105 return await callApi ( "/get_logs_batch" , payload ) ;
106106}
107107
108+ function sqlString ( value ) {
109+ return `'${ String ( value ?? "" ) . replaceAll ( "'" , "''" ) } '` ;
110+ }
111+
112+ function scalarLogsFromQueryRows ( rows ) {
113+ return ( rows || [ ] ) . map ( ( row ) => {
114+ const metrics = ( ( ) => {
115+ try {
116+ return JSON . parse ( row . metrics || "{}" ) ;
117+ } catch {
118+ return { } ;
119+ }
120+ } ) ( ) ;
121+ return {
122+ ...metrics ,
123+ timestamp : row . timestamp ,
124+ step : row . step ,
125+ } ;
126+ } ) ;
127+ }
128+
129+ function scalarOnlyLogs ( logs ) {
130+ return ( logs || [ ] ) . map ( ( row ) => {
131+ const out = {
132+ timestamp : row . timestamp ,
133+ step : row . step ,
134+ } ;
135+ for ( const [ key , value ] of Object . entries ( row ) ) {
136+ if ( key === "timestamp" || key === "step" ) continue ;
137+ if ( typeof value === "number" && Number . isFinite ( value ) ) out [ key ] = value ;
138+ }
139+ return out ;
140+ } ) ;
141+ }
142+
143+ async function queryProject ( project , query ) {
144+ return await callApi ( "/query_project" , { project, query } ) ;
145+ }
146+
147+ export async function getTraceStepCounts ( project , run ) {
148+ if ( await isStaticMode ( ) ) return [ ] ;
149+
150+ const normalized = normalizeRun ( run ) ;
151+ const where = normalized . run_id
152+ ? `m.run_id = ${ sqlString ( normalized . run_id ) } `
153+ : `m.run_name = ${ sqlString ( normalized . run ) } ` ;
154+ const result = await queryProject (
155+ project ,
156+ `
157+ SELECT
158+ m.step AS step,
159+ SUM(
160+ CASE
161+ WHEN json_type(j.value) = 'array' THEN json_array_length(j.value)
162+ ELSE 1
163+ END
164+ ) AS trace_count
165+ FROM metrics m, json_each(CAST(m.metrics AS TEXT)) j
166+ WHERE ${ where }
167+ AND m.step IS NOT NULL
168+ AND (
169+ (
170+ json_type(j.value) = 'array'
171+ AND (
172+ json_extract(j.value, '$[0]._type') = 'trackio.trace'
173+ OR json_extract(j.value, '$[0].messages') IS NOT NULL
174+ )
175+ )
176+ OR (
177+ json_type(j.value) = 'object'
178+ AND (
179+ json_extract(j.value, '$._type') = 'trackio.trace'
180+ OR json_extract(j.value, '$.messages') IS NOT NULL
181+ )
182+ )
183+ )
184+ GROUP BY m.step
185+ ORDER BY m.step
186+ ` ,
187+ ) ;
188+ return ( result . rows || [ ] ) . map ( ( row ) => ( {
189+ step : Number ( row . step ) ,
190+ count : Number ( row . trace_count || 0 ) ,
191+ } ) ) . filter ( ( row ) => Number . isFinite ( row . step ) && row . count > 0 ) ;
192+ }
193+
194+ export async function getScalarLogsBatch ( project , runs ) {
195+ if ( await isStaticMode ( ) ) {
196+ const out = [ ] ;
197+ for ( const run of runs ) {
198+ const logs = await staticApi . getLogs ( project , run ) ;
199+ out . push ( { ...normalizeRun ( run ) , logs : scalarOnlyLogs ( logs ) } ) ;
200+ }
201+ return out ;
202+ }
203+
204+ const out = [ ] ;
205+ for ( const run of runs ) {
206+ const normalized = normalizeRun ( run ) ;
207+ const where = normalized . run_id
208+ ? `m.run_id = ${ sqlString ( normalized . run_id ) } `
209+ : `m.run_name = ${ sqlString ( normalized . run ) } ` ;
210+ const result = await queryProject (
211+ project ,
212+ `
213+ SELECT
214+ m.timestamp,
215+ m.step,
216+ json_group_object(j.key, j.value) AS metrics
217+ FROM metrics m, json_each(CAST(m.metrics AS TEXT)) j
218+ WHERE ${ where }
219+ AND json_type(j.value) IN ('integer', 'real')
220+ GROUP BY m.id, m.timestamp, m.step
221+ ORDER BY m.timestamp
222+ ` ,
223+ ) ;
224+ out . push ( {
225+ ...normalized ,
226+ logs : scalarLogsFromQueryRows ( result . rows ) ,
227+ } ) ;
228+ }
229+ return out ;
230+ }
231+
108232export async function getTraces ( project , run , options = { } ) {
109233 const params = { project, ...normalizeRun ( run ) , ...options } ;
110234 if ( await isStaticMode ( ) ) return staticApi . getTraces ( project , run , options ) ;
0 commit comments