Skip to content

Commit 394d18b

Browse files
authored
feat: switch to uplot for prom charts (#371)
* feat: switch to uplot for prom charts * clippy clappy * review
1 parent 88f1b21 commit 394d18b

File tree

8 files changed

+268
-81
lines changed

8 files changed

+268
-81
lines changed

bun.lock

Lines changed: 6 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2-
import type { PrometheusSeries } from "./PrometheusSeries";
32
import type { PrometheusTimeRange } from "./PrometheusTimeRange";
43

5-
export type PrometheusQueryResult = { series: Array<PrometheusSeries>, queryExecuted: string, timeRange: PrometheusTimeRange, };
4+
export type PrometheusQueryResult = {
5+
/**
6+
* Columnar data for uPlot: [timestamps, series1_values, series2_values, ...]
7+
* Timestamps are in seconds (Unix epoch)
8+
*/
9+
data: Array<Array<number>>,
10+
/**
11+
* Series names in order (index i corresponds to data[i+1])
12+
*/
13+
seriesNames: Array<string>, queryExecuted: string, timeRange: PrometheusTimeRange, };

crates/atuin-desktop-runtime/src/blocks/prometheus.rs

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,15 @@ pub type PrometheusConnection = (Client, String, PrometheusTimeRange);
1717
#[serde(rename_all = "camelCase")]
1818
#[ts(export)]
1919
pub struct PrometheusQueryResult {
20-
series: Vec<PrometheusSeries>,
20+
/// Columnar data for uPlot: [timestamps, series1_values, series2_values, ...]
21+
/// Timestamps are in seconds (Unix epoch)
22+
data: Vec<Vec<f64>>,
23+
/// Series names in order (index i corresponds to data[i+1])
24+
series_names: Vec<String>,
2125
query_executed: String,
2226
time_range: PrometheusTimeRange,
2327
}
2428

25-
#[derive(Debug, Clone, Serialize, TS)]
26-
#[serde(rename_all = "camelCase")]
27-
#[ts(export)]
28-
pub struct PrometheusSeries {
29-
#[serde(rename = "type")]
30-
series_type: String,
31-
show_symbol: bool,
32-
name: String,
33-
data: Vec<(f64, f64)>,
34-
}
35-
3629
#[derive(Debug, Clone, Serialize, TS)]
3730
#[serde(rename_all = "camelCase")]
3831
#[ts(export)]
@@ -57,7 +50,7 @@ pub struct PrometheusBlockOutput {
5750
impl PrometheusBlockOutput {
5851
/// Create a new PrometheusBlockOutput from query results
5952
pub fn new(results: Vec<PrometheusQueryResult>) -> Self {
60-
let total_series = results.iter().map(|r| r.series.len()).sum();
53+
let total_series = results.iter().map(|r| r.series_names.len()).sum();
6154
Self {
6255
results,
6356
total_series,
@@ -82,9 +75,12 @@ impl BlockExecutionOutput for PrometheusBlockOutput {
8275
"result_count" => Some(minijinja::Value::from(self.results.len())),
8376

8477
// Convenience accessors for first result
85-
"series" => self
78+
"series_names" => self
79+
.first_result()
80+
.map(|r| minijinja::Value::from_serialize(&r.series_names)),
81+
"data" => self
8682
.first_result()
87-
.map(|r| minijinja::Value::from_serialize(&r.series)),
83+
.map(|r| minijinja::Value::from_serialize(&r.data)),
8884
"query_executed" => self
8985
.first_result()
9086
.map(|r| minijinja::Value::from(r.query_executed.clone())),
@@ -102,7 +98,8 @@ impl BlockExecutionOutput for PrometheusBlockOutput {
10298
"first",
10399
"total_series",
104100
"result_count",
105-
"series",
101+
"series_names",
102+
"data",
106103
"query_executed",
107104
"time_range",
108105
])
@@ -418,14 +415,17 @@ impl QueryBlockBehavior for Prometheus {
418415
PrometheusBlockError::QueryError("Missing result field in data".to_string())
419416
})?;
420417

421-
let mut series = Vec::new();
418+
let mut series_names: Vec<String> = Vec::new();
419+
let mut all_values: Vec<Vec<f64>> = Vec::new();
420+
let mut timestamps: Vec<f64> = Vec::new();
422421

423422
if let Some(result_array) = result.as_array() {
424423
for series_data in result_array {
425424
if let (Some(metric), Some(values)) = (
426425
series_data.get("metric"),
427426
series_data.get("values").and_then(|v| v.as_array()),
428427
) {
428+
// Build series name from metric labels
429429
let series_name = if let Some(metric_obj) = metric.as_object() {
430430
if metric_obj.is_empty() {
431431
query.to_string()
@@ -440,32 +440,42 @@ impl QueryBlockBehavior for Prometheus {
440440
query.to_string()
441441
};
442442

443-
let mut data_points = Vec::new();
443+
// Extract timestamps (only from first series - all share same timestamps)
444+
// and values for this series
445+
let mut series_values: Vec<f64> = Vec::new();
444446
for value_pair in values {
445447
if let Some(pair) = value_pair.as_array() {
446448
if pair.len() == 2 {
447-
let timestamp = pair[0].as_f64().unwrap_or(0.0) * 1000.0;
449+
// Timestamps in seconds (uPlot native format)
450+
let ts = pair[0].as_f64().unwrap_or(0.0);
448451
let value = pair[1]
449452
.as_str()
450453
.and_then(|s| s.parse::<f64>().ok())
451454
.unwrap_or(0.0);
452-
data_points.push((timestamp, value));
455+
456+
// Only collect timestamps from first series
457+
if series_names.is_empty() {
458+
timestamps.push(ts);
459+
}
460+
series_values.push(value);
453461
}
454462
}
455463
}
456464

457-
series.push(PrometheusSeries {
458-
series_type: "line".to_string(),
459-
show_symbol: false,
460-
name: series_name,
461-
data: data_points,
462-
});
465+
series_names.push(series_name);
466+
all_values.push(series_values);
463467
}
464468
}
465469
}
466470

471+
// Build columnar data: [timestamps, series1_values, series2_values, ...]
472+
let mut data: Vec<Vec<f64>> = Vec::with_capacity(1 + all_values.len());
473+
data.push(timestamps);
474+
data.extend(all_values);
475+
467476
let result = PrometheusQueryResult {
468-
series,
477+
data,
478+
series_names,
469479
query_executed: query.to_string(),
470480
time_range: time_range.clone(),
471481
};

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
"@iconify/icons-lucide": "^1.2.135",
4848
"@iconify/icons-solar": "^1.2.3",
4949
"@internationalized/date": "^3.7.0",
50-
"@kbox-labs/react-echarts": "^1.4.2",
5150
"@prometheus-io/codemirror-promql": "^0.53.3",
5251
"@radix-ui/react-dialog": "^1.1.6",
5352
"@radix-ui/react-dropdown-menu": "^2.1.6",
@@ -100,7 +99,6 @@
10099
"cmdk": "^1.0.4",
101100
"codemirror-lang-hcl": "0.0.0-beta.2",
102101
"date-fns": "^3.6.0",
103-
"echarts": "^5.6.0",
104102
"emittery": "^1.1.0",
105103
"flexsearch": "^0.7.43",
106104
"framer-motion": "^11.18.2",
@@ -141,6 +139,8 @@
141139
"tailwindcss-animate": "^1.0.7",
142140
"ts-tiny-activerecord": "git+https://github.com/atuinsh/ts-tiny-activerecord#v0.0.6",
143141
"undent": "^1.0.0",
142+
"uplot": "^1.6.32",
143+
"uplot-react": "^1.2.4",
144144
"use-resize-observer": "^9.1.0",
145145
"usehooks-ts": "^3.1.1",
146146
"uuidv7": "^1.0.2",

src/components/runbooks/editor/blocks/Prometheus/Prometheus.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ const Prometheus = ({
105105
}: PromProps) => {
106106
let editor = useBlockNoteEditor();
107107
const [value, setValue] = useState<string>(prometheus.query);
108-
const [data, setData] = useState<any[]>([]);
109-
const [config, _setConfig] = useState<{}>({});
108+
const [data, setData] = useState<Array<Array<number>>>([]);
109+
const [seriesNames, setSeriesNames] = useState<string[]>([]);
110110
const [timeFrame, setTimeFrame] = useState<TimeFrame>(
111111
timeOptions.find((t) => t.short === prometheus.period) || timeOptions[3],
112112
);
@@ -121,7 +121,8 @@ const Prometheus = ({
121121
if (output.object) {
122122
// Backend returns PrometheusQueryResult in the object field
123123
const result = output.object as PrometheusQueryResult;
124-
setData(result.series as any[]);
124+
setData(result.data);
125+
setSeriesNames(result.seriesNames);
125126
}
126127
});
127128

@@ -375,9 +376,11 @@ const Prometheus = ({
375376
) : execution.isError ? (
376377
<ErrorCard error={execution.error} />
377378
) : isRunning && data.length === 0 ? (
378-
<Spinner />
379+
<div className="flex items-center justify-center h-full w-full">
380+
<Spinner />
381+
</div>
379382
) : (
380-
<PromLineChart data={data} config={config} />
383+
<PromLineChart data={data} seriesNames={seriesNames} />
381384
)}
382385
</div>
383386
</Block>

0 commit comments

Comments
 (0)