update column settings on sql change

This commit is contained in:
dal 2025-04-08 13:25:19 -06:00
parent 634e5f0460
commit 4c5d6ca97d
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
2 changed files with 125 additions and 60 deletions

View File

@ -85,9 +85,10 @@ pub async fn get_metric_handler(
password: Option<String>,
) -> Result<BusterMetric> {
// 1. Fetch metric file with permission
let metric_file_with_permission_option = fetch_metric_file_with_permissions(metric_id, &user.id)
.await
.map_err(|e| anyhow!("Failed to fetch metric file with permissions: {}", e))?;
let metric_file_with_permission_option =
fetch_metric_file_with_permissions(metric_id, &user.id)
.await
.map_err(|e| anyhow!("Failed to fetch metric file with permissions: {}", e))?;
let metric_file_with_permission = if let Some(mf) = metric_file_with_permission_option {
mf
@ -98,7 +99,7 @@ pub async fn get_metric_handler(
let metric_file = metric_file_with_permission.metric_file;
let direct_permission_level = metric_file_with_permission.permission;
// 2. Determine the user's access level and enforce access rules
let permission: AssetPermissionRole;
tracing::debug!(metric_id = %metric_id, user_id = %user.id, "Checking permissions for metric");
@ -130,7 +131,7 @@ pub async fn get_metric_handler(
return Err(anyhow!("You don't have permission to view this metric"));
}
tracing::debug!(metric_id = %metric_id, "Metric is publicly accessible.");
// Check if the public access has expired
if let Some(expiry_date) = metric_file.public_expiry_date {
tracing::debug!(metric_id = %metric_id, ?expiry_date, "Checking expiry date");
@ -139,7 +140,7 @@ pub async fn get_metric_handler(
return Err(anyhow!("Public access to this metric has expired"));
}
}
// Check if a password is required
tracing::debug!(metric_id = %metric_id, has_password = metric_file.public_password.is_some(), "Checking password requirement");
if let Some(required_password) = &metric_file.public_password {
@ -179,42 +180,53 @@ pub async fn get_metric_handler(
}
});
// Determine which version to use based on version_number parameter
let (metric_content, version_num) = if let Some(version) = version_number {
// Get the specific version if it exists
if let Some(v) = metric_file.version_history.get_version(version) {
// Determine which content and version number to use
let metric_content: database::types::MetricYml;
let version_num: i32;
let current_data_metadata: Option<database::types::DataMetadata> = metric_file.data_metadata;
if let Some(requested_version) = version_number {
// --- Specific version requested ---
tracing::debug!(metric_id = %metric_id, version = requested_version, "Attempting to retrieve specific version");
if let Some(v) = metric_file.version_history.get_version(requested_version) {
match &v.content {
database::types::VersionContent::MetricYml(content) => {
(content.clone(), v.version_number)
metric_content = (**content).clone(); // Deref the Box and clone
version_num = v.version_number;
tracing::debug!(metric_id = %metric_id, version = requested_version, "Successfully retrieved specific version content");
}
_ => {
tracing::error!(metric_id = %metric_id, version = requested_version, "Invalid content type found for requested version");
return Err(anyhow!(
"Invalid content type found for version {}",
requested_version
));
}
_ => return Err(anyhow!("Invalid version content type")),
}
} else {
return Err(anyhow!("Version {} not found", version));
tracing::warn!(metric_id = %metric_id, version = requested_version, "Requested version not found in history");
return Err(anyhow!("Version {} not found", requested_version));
}
} else {
// Get the latest version
if let Some(v) = metric_file.version_history.get_latest_version() {
match &v.content {
database::types::VersionContent::MetricYml(content) => {
(content.clone(), v.version_number)
}
_ => return Err(anyhow!("Invalid version content type")),
}
} else {
// Fall back to current content if no version history
(Box::new(metric_file.content.clone()), 1)
// --- No specific version requested - use current state from the main table row ---
tracing::debug!(metric_id = %metric_id, "No specific version requested, using current metric file content");
metric_content = metric_file.content; // Use the content directly from the fetched MetricFile
// Determine the latest version number from history, defaulting to 1 if none
version_num = metric_file.version_history.get_version_number();
tracing::debug!(metric_id = %metric_id, latest_version = version_num, "Determined latest version number");
}
// Convert the selected content to pretty YAML for the 'file' field
let file = match serde_yaml::to_string(&metric_content) {
Ok(yaml) => yaml,
Err(e) => {
tracing::error!(metric_id = %metric_id, error = %e, "Failed to serialize selected metric content to YAML");
return Err(anyhow!("Failed to convert metric content to YAML: {}", e));
}
};
// Convert content to pretty YAML
let file = match serde_yaml::to_string(&metric_content) {
Ok(yaml) => yaml,
Err(e) => return Err(anyhow!("Failed to convert content to YAML: {}", e)),
};
// Data metadata is fetched directly from the metric_file database record
let data_metadata = metric_file.data_metadata;
// Data metadata always comes from the main table record (current state)
let data_metadata = current_data_metadata;
let mut conn = get_pg_pool().get().await?;
@ -368,6 +380,6 @@ pub async fn get_metric_handler(
publicly_accessible: metric_file.publicly_accessible,
public_expiry_date: metric_file.public_expiry_date,
public_enabled_by: public_enabled_by_user,
public_password: metric_file.public_password
public_password: metric_file.public_password,
})
}

View File

@ -14,6 +14,7 @@ use query_engine::data_source_query_routes::query_engine::query_engine;
use serde_json::Value;
use sharing::check_permission_access;
use uuid::Uuid;
use indexmap;
/// Recursively merges two JSON objects.
/// The second object (update) takes precedence over the first (base) where there are conflicts.
@ -204,20 +205,6 @@ pub async fn update_metric_handler(
content
};
// Calculate the next version number
let next_version = metric.versions.len() as i32 + 1;
// Only add a new version if update_version is true (defaults to true)
let should_update_version = request.update_version.unwrap_or(true);
// Add the new version to the version history or update the latest version
if should_update_version {
current_version_history.add_version(next_version, content.clone());
} else {
// Overwrite the current version instead of creating a new one
current_version_history.update_latest_version(content.clone());
}
// Calculate data_metadata if SQL changed
let data_metadata = if request.sql.is_some()
|| request.file.is_some()
@ -241,24 +228,74 @@ pub async fn update_metric_handler(
.await
.map_err(|e| anyhow!("Failed to execute SQL for metadata calculation: {}", e))?;
// Generate default column formats based on metadata using the new method
let default_formats =
// Generate default column formats based ONLY on the new metadata
let default_formats_map: indexmap::IndexMap<String, ColumnLabelFormat> =
ColumnLabelFormat::generate_formats_from_metadata(&query_result.metadata);
// Get existing chart config
let existing_config = serde_json::to_value(&content.chart_config)?;
// Get mutable access to the BaseChartConfig within the ChartConfig enum
let base_chart_config = match &mut content.chart_config {
database::types::ChartConfig::Bar(config) => &mut config.base,
database::types::ChartConfig::Line(config) => &mut config.base,
database::types::ChartConfig::Scatter(config) => &mut config.base,
database::types::ChartConfig::Pie(config) => &mut config.base,
database::types::ChartConfig::Combo(config) => &mut config.base,
database::types::ChartConfig::Metric(config) => &mut config.base,
database::types::ChartConfig::Table(config) => &mut config.base,
};
// Create a new JSON object with column_label_formats
let column_formats_json = serde_json::to_value(&default_formats)?;
let format_update = serde_json::json!({
"columnLabelFormats": column_formats_json
});
// Clone existing formats for comparison
let existing_formats_map = base_chart_config.column_label_formats.clone();
// Merge the formats with existing config
let merged_config = merge_json_objects(existing_config, format_update)?;
// Clear column_settings since the SQL has changed and old settings might
// reference columns that no longer exist in the result set
base_chart_config.column_settings = None;
// Also clear other column-specific configurations that might be invalidated by SQL changes
if let Some(trendlines) = &mut base_chart_config.trendlines {
trendlines.clear();
}
// Update the content's chart_config
content.chart_config = serde_json::from_value(merged_config)?;
// Build the final map, starting empty. This ensures only columns from the
// new metadata are included.
let mut final_formats_map = indexmap::IndexMap::new();
// Iterate through the new defaults (columns guaranteed to exist in the new SQL result)
for (column_name, new_default_format) in default_formats_map {
let final_format = match existing_formats_map.get(&column_name) {
// Column existed before, merge existing customizations onto new default structure
Some(existing_format) => {
// Convert both to Value for merging
// We need to clone new_default_format as it's moved in the loop otherwise
let new_default_value = serde_json::to_value(new_default_format.clone())?;
let existing_value = serde_json::to_value(existing_format.clone())?;
// Merge existing settings onto the default structure
let merged_value = merge_json_objects(new_default_value, existing_value)?;
// Attempt to deserialize back into ColumnLabelFormat
match serde_json::from_value::<ColumnLabelFormat>(merged_value) {
Ok(merged_format) => merged_format,
Err(e) => {
// Log the error and fallback to the new default format from metadata
tracing::warn!(
metric_id = %metric_id,
column_name = %column_name,
error = %e,
"Failed to merge existing column format. Using default format from new metadata."
);
// Use the original default format from the map iteration (before clone)
new_default_format
}
}
}
// Column is new, use the generated default format
None => new_default_format,
};
final_formats_map.insert(column_name, final_format);
}
// Replace the formats in the base config with the newly constructed map
base_chart_config.column_label_formats = final_formats_map;
// Return metadata
Some(query_result.metadata)
@ -266,6 +303,22 @@ pub async fn update_metric_handler(
None
};
// Calculate the next version number
let next_version = metric.versions.len() as i32 + 1;
// Only add a new version if update_version is true (defaults to true)
let should_update_version = request.update_version.unwrap_or(true);
// Add the new version to the version history or update the latest version
// IMPORTANT: This happens AFTER we've updated the column_label_formats
// to ensure the version history captures those changes
if should_update_version {
current_version_history.add_version(next_version, content.clone());
} else {
// Overwrite the current version instead of creating a new one
current_version_history.update_latest_version(content.clone());
}
// Convert content to JSON for storage
let content_json = serde_json::to_value(content.clone())?;