mirror of https://github.com/buster-so/buster.git
Merge branch 'evals' of https://github.com/buster-so/buster into evals
This commit is contained in:
commit
645b5940d8
|
@ -205,6 +205,7 @@ You can create, update, or modify the following assets, which are automatically
|
|||
- **Review and Update**: After creation, metrics can be reviewed and updated individually or in bulk as needed.
|
||||
- **Use in Dashboards**: Metrics can be saved to dashboards for further use.
|
||||
- **Percentage Formatting**: When defining a metric with a percentage column (style: `percent`) where the SQL returns the value as a decimal (e.g., 0.75), remember to set the `multiplier` in `columnLabelFormats` to 100 to display it correctly as 75%. If the value is already represented as a percentage (e.g., 75), the multiplier should be 1 (or omitted as it defaults to 1).
|
||||
- **Date Grouping**: For metrics visualizing date columns on the X-axis (e.g., line or combo charts), remember to set the `xAxisTimeInterval` field within the `xAxisConfig` section of `chartConfig` to control how dates are grouped (e.g., `day`, `week`, `month`). This is crucial for meaningful time-series visualizations.
|
||||
|
||||
- **Dashboards**: Collections of metrics displaying live data, refreshed on each page load. Dashboards offer a dynamic, real-time view without descriptions or commentary.
|
||||
|
||||
|
|
|
@ -244,7 +244,7 @@ required:
|
|||
- chartConfig
|
||||
|
||||
definitions:
|
||||
# BASE CHART CONFIG (common parts required by ALL chart types)
|
||||
# BASE CHART CONFIG (common parts used by ALL chart types)
|
||||
base_chart_config:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -275,6 +275,10 @@ definitions:
|
|||
type: boolean
|
||||
gridLines:
|
||||
type: boolean
|
||||
showLegendHeadline:
|
||||
oneOf:
|
||||
- type: boolean
|
||||
- type: string
|
||||
goalLines:
|
||||
type: array
|
||||
items:
|
||||
|
@ -283,10 +287,105 @@ definitions:
|
|||
type: array
|
||||
items:
|
||||
$ref: #/definitions/trendline
|
||||
disableTooltip:
|
||||
type: boolean
|
||||
# Axis Configurations
|
||||
xAxisConfig:
|
||||
description: Optional X-axis configuration. Primarily used to set the `xAxisTimeInterval` for date axes (day, week, month, etc.). Other properties control label visibility, title, rotation, and zoom.
|
||||
$ref: '#/definitions/x_axis_config'
|
||||
yAxisConfig:
|
||||
description: Optional Y-axis configuration. Primarily used to set the `yAxisShowAxisLabel` and `yAxisShowAxisTitle` properties. Other properties control label visibility, title, rotation, and zoom.
|
||||
$ref: '#/definitions/y_axis_config'
|
||||
y2AxisConfig:
|
||||
description: Optional secondary Y-axis configuration. Used for combo charts.
|
||||
$ref: '#/definitions/y2_axis_config'
|
||||
categoryAxisStyleConfig:
|
||||
description: Optional style configuration for the category axis (color/grouping).
|
||||
$ref: '#/definitions/category_axis_style_config'
|
||||
required:
|
||||
- selectedChartType
|
||||
- columnLabelFormats
|
||||
|
||||
# AXIS CONFIGURATIONS
|
||||
x_axis_config:
|
||||
type: object
|
||||
properties:
|
||||
xAxisTimeInterval:
|
||||
type: string
|
||||
enum: [day, week, month, quarter, year, 'null']
|
||||
description: Time interval for X-axis (combo/line charts). Default: null.
|
||||
xAxisShowAxisLabel:
|
||||
type: boolean
|
||||
description: Show X-axis labels. Default: true.
|
||||
xAxisShowAxisTitle:
|
||||
type: boolean
|
||||
description: Show X-axis title. Default: true.
|
||||
xAxisAxisTitle:
|
||||
type: [string, 'null']
|
||||
description: X-axis title. Default: null (auto-generates from column names).
|
||||
xAxisLabelRotation:
|
||||
type: string # Representing numbers or 'auto'
|
||||
enum: ["0", "45", "90", auto]
|
||||
description: Label rotation. Default: auto.
|
||||
xAxisDataZoom:
|
||||
type: boolean
|
||||
description: Enable data zoom on X-axis. Default: false (User only).
|
||||
additionalProperties: false
|
||||
required:
|
||||
- xAxisTimeInterval
|
||||
|
||||
y_axis_config:
|
||||
type: object
|
||||
properties:
|
||||
yAxisShowAxisLabel:
|
||||
type: boolean
|
||||
description: Show Y-axis labels. Default: true.
|
||||
yAxisShowAxisTitle:
|
||||
type: boolean
|
||||
description: Show Y-axis title. Default: true.
|
||||
yAxisAxisTitle:
|
||||
type: [string, 'null']
|
||||
description: Y-axis title. Default: null (uses first plotted column name).
|
||||
yAxisStartAxisAtZero:
|
||||
type: [boolean, 'null']
|
||||
description: Start Y-axis at zero. Default: true.
|
||||
yAxisScaleType:
|
||||
type: string
|
||||
enum: [log, linear]
|
||||
description: Scale type for Y-axis. Default: linear.
|
||||
additionalProperties: false
|
||||
|
||||
y2_axis_config:
|
||||
type: object
|
||||
description: Secondary Y-axis configuration (for combo charts).
|
||||
properties:
|
||||
y2AxisShowAxisLabel:
|
||||
type: boolean
|
||||
description: Show Y2-axis labels. Default: true.
|
||||
y2AxisShowAxisTitle:
|
||||
type: boolean
|
||||
description: Show Y2-axis title. Default: true.
|
||||
y2AxisAxisTitle:
|
||||
type: [string, 'null']
|
||||
description: Y2-axis title. Default: null (uses first plotted column name).
|
||||
y2AxisStartAxisAtZero:
|
||||
type: [boolean, 'null']
|
||||
description: Start Y2-axis at zero. Default: true.
|
||||
y2AxisScaleType:
|
||||
type: string
|
||||
enum: [log, linear]
|
||||
description: Scale type for Y2-axis. Default: linear.
|
||||
additionalProperties: false
|
||||
|
||||
category_axis_style_config:
|
||||
type: object
|
||||
description: Style configuration for the category axis (color/grouping).
|
||||
properties:
|
||||
categoryAxisTitle:
|
||||
type: [string, 'null']
|
||||
description: Title for the category axis.
|
||||
additionalProperties: false
|
||||
|
||||
# COLUMN FORMATTING
|
||||
columnLabelFormat:
|
||||
type: object
|
||||
|
@ -305,7 +404,7 @@ definitions:
|
|||
# - If the value comes directly from a database column, use multiplier: 1
|
||||
# - If the value is calculated in your SQL query and not already multiplied by 100, use multiplier: 100
|
||||
- number
|
||||
- date
|
||||
- date # Note: For date columns, consider setting xAxisTimeInterval in xAxisConfig to control date grouping (day, week, month, quarter, year)
|
||||
- string
|
||||
multiplier:
|
||||
type: number
|
||||
|
|
|
@ -59,7 +59,7 @@ pub struct Message {
|
|||
pub feedback: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Queryable, Insertable, Debug)]
|
||||
#[derive(Queryable, Insertable, Debug, Clone)]
|
||||
#[diesel(table_name = messages_to_files)]
|
||||
pub struct MessageToFile {
|
||||
pub id: Uuid,
|
||||
|
|
|
@ -205,15 +205,19 @@ pub struct BaseChartConfig {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(alias = "disable_tooltip")]
|
||||
pub disable_tooltip: Option<bool>,
|
||||
// Updated Axis Configs using defined structs
|
||||
#[serde(flatten, default)] // Flatten includes fields directly, default handles Option
|
||||
pub y_axis_config: YAxisConfig,
|
||||
#[serde(flatten, default)]
|
||||
pub x_axis_config: XAxisConfig,
|
||||
#[serde(flatten, default)]
|
||||
pub category_axis_style_config: CategoryAxisStyleConfig,
|
||||
#[serde(flatten, default)]
|
||||
pub y2_axis_config: Y2AxisConfig,
|
||||
// Updated Axis Configs using defined structs (now optional)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(alias = "y_axis_config")]
|
||||
pub y_axis_config: Option<YAxisConfig>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(alias = "x_axis_config")]
|
||||
pub x_axis_config: Option<XAxisConfig>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(alias = "category_axis_style_config")]
|
||||
pub category_axis_style_config: Option<CategoryAxisStyleConfig>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(alias = "y2_axis_config")]
|
||||
pub y2_axis_config: Option<Y2AxisConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
|
|
|
@ -8,6 +8,7 @@ use database::{
|
|||
};
|
||||
use diesel::{insert_into, update, ExpressionMethods, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use futures::future::try_join_all;
|
||||
use middleware::AuthenticatedUser;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
|
@ -20,7 +21,7 @@ use crate::dashboards::{update_dashboard_handler, DashboardUpdateRequest};
|
|||
use crate::metrics::{update_metric_handler, UpdateMetricRequest};
|
||||
|
||||
/// Request structure for restoring an asset (metric or dashboard) version in a chat
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ChatRestoreRequest {
|
||||
/// ID of the asset to restore
|
||||
pub asset_id: Uuid,
|
||||
|
@ -41,84 +42,102 @@ pub struct ChatRestoreRequest {
|
|||
/// * `Result<ChatWithMessages>` - The updated chat with new messages documenting the restoration
|
||||
///
|
||||
/// # Process
|
||||
/// 1. Restores the specified asset version using the appropriate handler
|
||||
/// 2. Creates a text message in the chat documenting the restoration
|
||||
/// 3. Creates a file message linking to the restored asset
|
||||
/// 4. Updates the chat record with the latest file info
|
||||
/// 5. Returns the updated chat with all messages
|
||||
/// 1. Concurrently:
|
||||
/// a. Restores the specified asset version using the appropriate handler
|
||||
/// b. Fetches the most recent message from the chat (to copy raw_llm_messages)
|
||||
/// 2. Waits for restoration and fetch to complete.
|
||||
/// 3. Constructs new message details (text, file link, raw_llm_messages).
|
||||
/// 4. Concurrently:
|
||||
/// a. Inserts the new message documenting the restoration
|
||||
/// b. Inserts the message-to-file association
|
||||
/// c. Updates the chat record with the latest file info
|
||||
/// 5. Waits for insertions and update to complete.
|
||||
/// 6. Returns the updated chat with all messages
|
||||
pub async fn restore_chat_handler(
|
||||
chat_id: &Uuid,
|
||||
user: &AuthenticatedUser,
|
||||
request: ChatRestoreRequest,
|
||||
) -> Result<ChatWithMessages> {
|
||||
let mut conn = get_pg_pool().get().await?;
|
||||
// Clone variables needed for concurrent tasks
|
||||
let user_clone1 = user.clone();
|
||||
let request_clone1 = request.clone();
|
||||
let chat_id_clone1 = *chat_id;
|
||||
|
||||
// Step 1: Restore the asset using the appropriate handler
|
||||
let (file_type, file_name, file_id, version_number) = match request.asset_type {
|
||||
AssetType::MetricFile => {
|
||||
// Create a metric update request with only the restore_to_version parameter
|
||||
let metric_request = UpdateMetricRequest {
|
||||
restore_to_version: Some(request.version_number),
|
||||
..Default::default()
|
||||
};
|
||||
// Task 1: Restore Asset
|
||||
let restore_task = tokio::spawn(async move {
|
||||
let (file_type, file_name, file_id, version_number) = match request_clone1.asset_type {
|
||||
AssetType::MetricFile => {
|
||||
let metric_request = UpdateMetricRequest {
|
||||
restore_to_version: Some(request_clone1.version_number),
|
||||
..Default::default()
|
||||
};
|
||||
let updated_metric =
|
||||
update_metric_handler(&request_clone1.asset_id, &user_clone1, metric_request)
|
||||
.await?;
|
||||
(
|
||||
"metric".to_string(),
|
||||
updated_metric.name,
|
||||
updated_metric.id,
|
||||
updated_metric.versions.len() as i32,
|
||||
)
|
||||
}
|
||||
AssetType::DashboardFile => {
|
||||
let dashboard_request = DashboardUpdateRequest {
|
||||
restore_to_version: Some(request_clone1.version_number),
|
||||
..Default::default()
|
||||
};
|
||||
let updated_dashboard = update_dashboard_handler(
|
||||
request_clone1.asset_id,
|
||||
dashboard_request,
|
||||
&user_clone1,
|
||||
)
|
||||
.await?;
|
||||
(
|
||||
"dashboard".to_string(),
|
||||
updated_dashboard.dashboard.name,
|
||||
updated_dashboard.dashboard.id,
|
||||
updated_dashboard.dashboard.version_number,
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"Unsupported asset type for restoration: {:?}",
|
||||
request_clone1.asset_type
|
||||
))
|
||||
}
|
||||
};
|
||||
// Explicitly type the Ok variant for the compiler
|
||||
Ok::<_, anyhow::Error>((file_type, file_name, file_id, version_number))
|
||||
});
|
||||
|
||||
// Call the metric update handler through the public module function
|
||||
let updated_metric =
|
||||
update_metric_handler(&request.asset_id, user, metric_request).await?;
|
||||
// Task 2: Get the most recent message to copy raw_llm_messages
|
||||
let last_message_task = tokio::spawn(async move {
|
||||
let mut conn = get_pg_pool().get().await?;
|
||||
let last_message = messages::table
|
||||
.filter(messages::chat_id.eq(&chat_id_clone1))
|
||||
.filter(messages::deleted_at.is_null())
|
||||
.limit(1)
|
||||
.order_by(messages::created_at.desc())
|
||||
.first::<Message>(&mut conn) // Assuming Message derives Clone
|
||||
.await
|
||||
.ok();
|
||||
// Explicitly type the Ok variant
|
||||
Ok::<_, anyhow::Error>(last_message)
|
||||
});
|
||||
|
||||
// Return the file information
|
||||
(
|
||||
"metric".to_string(),
|
||||
updated_metric.name,
|
||||
updated_metric.id,
|
||||
updated_metric.versions.len() as i32, // Get version number from versions length
|
||||
)
|
||||
}
|
||||
AssetType::DashboardFile => {
|
||||
// Create a dashboard update request with only the restore_to_version parameter
|
||||
let dashboard_request = DashboardUpdateRequest {
|
||||
restore_to_version: Some(request.version_number),
|
||||
..Default::default()
|
||||
};
|
||||
// Wait for initial tasks to complete
|
||||
let (restore_result, last_message_result) = tokio::join!(restore_task, last_message_task);
|
||||
|
||||
// Call the dashboard update handler through the public module function
|
||||
let updated_dashboard =
|
||||
update_dashboard_handler(request.asset_id, dashboard_request, user).await?;
|
||||
// Handle potential errors from spawned tasks
|
||||
let (file_type, file_name, file_id, version_number) = restore_result??;
|
||||
let last_message = last_message_result??;
|
||||
|
||||
// Return the file information
|
||||
(
|
||||
"dashboard".to_string(),
|
||||
updated_dashboard.dashboard.name,
|
||||
updated_dashboard.dashboard.id,
|
||||
updated_dashboard.dashboard.version_number,
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"Unsupported asset type for restoration: {:?}",
|
||||
request.asset_type
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// Step 2: Get the most recent message to copy raw_llm_messages
|
||||
// Fetch the most recent message for the chat to extract raw_llm_messages
|
||||
let last_message = messages::table
|
||||
.filter(messages::chat_id.eq(chat_id))
|
||||
.filter(messages::deleted_at.is_null())
|
||||
.limit(1)
|
||||
// We need to use order here to get the latest message
|
||||
.then_order_by(messages::created_at.desc())
|
||||
.first::<Message>(&mut conn)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
// Create raw_llm_messages by copying from the previous message and adding restoration entries
|
||||
// Step 3: Construct message details
|
||||
let tool_call_id = format!("call_{}", Uuid::new_v4().to_string().replace("-", ""));
|
||||
|
||||
// Start with copied raw_llm_messages or an empty array
|
||||
let mut raw_llm_messages = if let Some(last_msg) = &last_message {
|
||||
if let Ok(msgs) = serde_json::from_value::<Vec<Value>>(last_msg.raw_llm_messages.clone()) {
|
||||
// Use clone here if last_message is Some(Message)
|
||||
if let Ok(msgs) = serde_json::from_value::<Vec<Value>>(last_msg.raw_llm_messages.clone())
|
||||
{
|
||||
msgs
|
||||
} else {
|
||||
Vec::new()
|
||||
|
@ -127,7 +146,6 @@ pub async fn restore_chat_handler(
|
|||
Vec::new()
|
||||
};
|
||||
|
||||
// Add tool call message and tool response message
|
||||
raw_llm_messages.push(json!({
|
||||
"name": "buster_super_agent",
|
||||
"role": "assistant",
|
||||
|
@ -144,8 +162,6 @@ pub async fn restore_chat_handler(
|
|||
}
|
||||
]
|
||||
}));
|
||||
|
||||
// Add the tool response
|
||||
raw_llm_messages.push(json!({
|
||||
"name": format!("restore_{}", file_type),
|
||||
"role": "tool",
|
||||
|
@ -156,13 +172,10 @@ pub async fn restore_chat_handler(
|
|||
"tool_call_id": tool_call_id
|
||||
}));
|
||||
|
||||
// Step 3: Create a message with text and file responses
|
||||
|
||||
let message_id = Uuid::new_v4();
|
||||
let now = Utc::now();
|
||||
let timestamp = now.timestamp();
|
||||
|
||||
// Create response messages array with both text and file response
|
||||
let response_messages = json!([
|
||||
{
|
||||
"id": file_id.to_string(),
|
||||
|
@ -174,21 +187,22 @@ pub async fn restore_chat_handler(
|
|||
"timestamp": timestamp
|
||||
}
|
||||
],
|
||||
"file_name": file_name,
|
||||
"file_type": file_type,
|
||||
"version_number": version_number,
|
||||
"file_name": file_name, // file_name is already String, no clone needed if moved
|
||||
"file_type": file_type, // file_type is already String, no clone needed if moved
|
||||
"version_number": version_number, // version_number is i32 (Copy)
|
||||
"filter_version_id": null
|
||||
}
|
||||
]);
|
||||
|
||||
// Create a Message object to insert
|
||||
// Create Message object - requires Message to be Clone if used in multiple tasks
|
||||
// Assuming Message derives Clone
|
||||
let message = Message {
|
||||
id: message_id,
|
||||
request_message: None, // Empty request message as per requirement
|
||||
response_messages: response_messages,
|
||||
request_message: None,
|
||||
response_messages, // This is Value, likely Clone
|
||||
reasoning: json!([]),
|
||||
title: "Version Restoration".to_string(),
|
||||
raw_llm_messages: Value::Array(raw_llm_messages.clone()),
|
||||
raw_llm_messages: Value::Array(raw_llm_messages), // raw_llm_messages moved here
|
||||
final_reasoning_message: Some(format!(
|
||||
"v{} was created by restoring v{}",
|
||||
version_number, request.version_number
|
||||
|
@ -201,13 +215,8 @@ pub async fn restore_chat_handler(
|
|||
feedback: None,
|
||||
};
|
||||
|
||||
// Insert the message
|
||||
diesel::insert_into(messages::table)
|
||||
.values(&message)
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
// Create the message-to-file association
|
||||
// Create MessageToFile object - requires MessageToFile to be Clone if used in multiple tasks
|
||||
// Assuming MessageToFile derives Clone
|
||||
let message_to_file = MessageToFile {
|
||||
id: Uuid::new_v4(),
|
||||
message_id: message_id,
|
||||
|
@ -219,23 +228,50 @@ pub async fn restore_chat_handler(
|
|||
version_number: version_number,
|
||||
};
|
||||
|
||||
// Insert the message-to-file association into the database
|
||||
diesel::insert_into(messages_to_files::table)
|
||||
.values(&message_to_file)
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
// Step 4: Concurrently insert message, message_to_file, and update chat
|
||||
// Clone necessary variables for final tasks
|
||||
let message_clone = message.clone(); // Requires Message: Clone
|
||||
let message_to_file_clone = message_to_file.clone(); // Requires MessageToFile: Clone
|
||||
let chat_id_clone2 = *chat_id;
|
||||
let request_asset_type_clone = request.asset_type; // AssetType is likely Copy
|
||||
let file_id_clone = file_id; // file_id is Uuid (Copy)
|
||||
|
||||
// Step 4: Update the chat record with the latest file info
|
||||
update(chats::table)
|
||||
.filter(chats::id.eq(chat_id))
|
||||
.set((
|
||||
chats::most_recent_file_id.eq(Some(file_id)),
|
||||
chats::most_recent_version_number.eq(Some(version_number)),
|
||||
chats::most_recent_file_type.eq(Some(request.asset_type.to_string())),
|
||||
chats::updated_at.eq(now),
|
||||
))
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
let insert_message_task = tokio::spawn(async move {
|
||||
let mut conn = get_pg_pool().get().await?;
|
||||
diesel::insert_into(messages::table)
|
||||
.values(&message_clone) // Use cloned message
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
Ok::<_, anyhow::Error>(()) // Explicit Ok type
|
||||
});
|
||||
|
||||
let insert_mtf_task = tokio::spawn(async move {
|
||||
let mut conn = get_pg_pool().get().await?;
|
||||
diesel::insert_into(messages_to_files::table)
|
||||
.values(&message_to_file_clone) // Use cloned mtf
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
Ok::<_, anyhow::Error>(()) // Explicit Ok type
|
||||
});
|
||||
|
||||
let update_chat_task = tokio::spawn(async move {
|
||||
let mut conn = get_pg_pool().get().await?;
|
||||
update(chats::table)
|
||||
.filter(chats::id.eq(&chat_id_clone2))
|
||||
.set((
|
||||
chats::most_recent_file_id.eq(Some(file_id_clone)),
|
||||
chats::most_recent_version_number.eq(Some(version_number)), // version_number is Copy
|
||||
chats::most_recent_file_type.eq(Some(request_asset_type_clone.to_string())),
|
||||
chats::updated_at.eq(now), // now is Copy
|
||||
))
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
Ok::<_, anyhow::Error>(()) // Explicit Ok type
|
||||
});
|
||||
|
||||
// Wait for final database operations using try_join_all for cleaner error handling
|
||||
try_join_all(vec![insert_message_task, insert_mtf_task, update_chat_task]).await?;
|
||||
|
||||
// Return the updated chat with messages
|
||||
get_chat_handler(chat_id, user, false).await
|
||||
|
|
Loading…
Reference in New Issue