From 780b7cf89bf191aa9de46765fb375f6c45fca2d2 Mon Sep 17 00:00:00 2001 From: Subhra264 Date: Wed, 7 Jan 2026 14:16:41 +0530 Subject: [PATCH] fix: remove delete bad dashboards migration (#9860) ### **User description** From v0.14.6-rc9 to v0.40.0 migration is currently broken for dashboards with label position . This pr fixes this by removing the migration. The dashboard migration generally happens through UI, no migration needed by backend. ___ ### **PR Type** Bug fix ___ ### **Description** - Remove `delete_bad_dashboards` migration logic - Convert `up` function into no-op stub - Delete JSON parsing and schema definitions ___

File Walkthrough

Relevant files
Bug fix
m20250107_160900_delete_bad_dashboards.rs`
Simplify delete_bad_dashboards migration                                 

`src/infra/src/table/migration/m20250107_160900_delete_bad_dashboards.rs`
  • Dropped chrono and sea_orm imports
  • Simplified up to do nothing
  • Removed can_parse_data and version modules
___ --- .../m20250107_160900_delete_bad_dashboards.rs | 1711 +---------------- 1 file changed, 13 insertions(+), 1698 deletions(-) diff --git a/src/infra/src/table/migration/m20250107_160900_delete_bad_dashboards.rs b/src/infra/src/table/migration/m20250107_160900_delete_bad_dashboards.rs index dd15577c45..182b9d22da 100644 --- a/src/infra/src/table/migration/m20250107_160900_delete_bad_dashboards.rs +++ b/src/infra/src/table/migration/m20250107_160900_delete_bad_dashboards.rs @@ -13,13 +13,14 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//! Removes records from the dashboards table whose JSON data column cannot be -//! parsed into a dashboard. +//! Migration stub for m20250107_160900_delete_bad_dashboards. +//! +//! This migration was originally intended to remove dashboards with unparseable +//! JSON data, but was found to incorrectly delete valid dashboards with certain +//! label/legend position configurations. It has been disabled since dashboard +//! schema migrations are handled by the UI layer. The migration remains in the +//! sequence to maintain migration ordering consistency. -use chrono::{DateTime, FixedOffset}; -use sea_orm::{ - ActiveModelTrait, EntityTrait, IntoActiveModel, PaginatorTrait, QueryOrder, TransactionTrait, -}; use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] @@ -27,25 +28,12 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let txn = manager.get_connection().begin().await?; - - // Migrate pages of 100 records at a time to avoid loading too many - // records into memory. - let mut pages = sea_orm_dashboards::Entity::find() - .order_by_asc(sea_orm_dashboards::Column::Id) - .paginate(&txn, 100); - - while let Some(dashboards) = pages.fetch_and_next().await? { - for d in dashboards { - if !can_parse_data(&d) { - let active_model = d.into_active_model(); - active_model.delete(&txn).await?; - } - } - } - - txn.commit().await?; + async fn up(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + // This migration was disabled because it incorrectly deleted valid dashboards + // that had label/legend position fields that didn't match the v1-v5 schema + // definitions. Dashboard schema migrations are now handled by the UI layer. + // See PR #9860 for details. + // Do nothing Ok(()) } @@ -54,1676 +42,3 @@ impl MigrationTrait for Migration { Ok(()) } } - -/// Returns true if the dashboard records JSON `data` field can be parsed into -/// v1, v2, v3, v4, or v5 dashboard. -pub fn can_parse_data(model: &sea_orm_dashboards::Model) -> bool { - let mut data = model.data.clone(); - - if let Some(obj) = data.as_object_mut() { - // The domain model JSON deserialization logic for v1-v5 expects - // some or all these fields to be present in the JSON even though we - // store them in DB columns. Therefore we add these values back into - // the JSON object so that deserializing the JSON can succeed. - obj.insert("dashboardId".to_owned(), model.dashboard_id.clone().into()); - obj.insert("version".to_owned(), model.version.into()); - obj.insert("owner".to_owned(), model.owner.clone().into()); - obj.insert( - "role".to_owned(), - model.role.clone().unwrap_or_default().into(), - ); - obj.insert("title".to_owned(), model.title.clone().into()); - obj.insert( - "description".to_owned(), - model.description.clone().unwrap_or_default().into(), - ); - } - - match model.version { - 1 => { - let parsed: Result = serde_json::from_value(data); - parsed.is_ok() - } - 2 => { - let parsed: Result = serde_json::from_value(data); - parsed.is_ok() - } - 3 => { - let parsed: Result = serde_json::from_value(data); - parsed.is_ok() - } - 4 => { - let parsed: Result = serde_json::from_value(data); - parsed.is_ok() - } - 5 => { - let parsed: Result = serde_json::from_value(data); - parsed.is_ok() - } - _ => false, - } -} - -// The schemas of tables might change after subsequent migrations. Therefore -// this migration only references ORM models in private submodules that should -// remain unchanged rather than ORM models in the `entity` module that will be -// updated to reflect the latest changes to table schemas. - -/// Representation of the dashboards table at the time this migration executes. -mod sea_orm_dashboards { - use sea_orm::entity::prelude::*; - - #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] - #[sea_orm(table_name = "dashboards")] - pub struct Model { - #[sea_orm(primary_key)] - pub id: String, - pub dashboard_id: String, - pub folder_id: String, - pub owner: String, - pub role: Option, - pub title: String, - #[sea_orm(column_type = "Text", nullable)] - pub description: Option, - pub data: Json, - pub version: i32, - pub created_at: i64, - } - - // There are relations but they are not important to this migration. - #[derive(Clone, Debug, EnumIter, DeriveRelation)] - pub enum Relation {} - - impl ActiveModelBehavior for ActiveModel {} -} - -/// Representation of the v1 dashboard inner data schema at the time this -/// migration executes. -#[allow(dead_code)] -mod v1 { - use chrono::{DateTime, FixedOffset}; - use serde::Deserialize; - - use super::{datetime_now, stream::StreamType}; - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Dashboard { - #[serde(default)] - pub dashboard_id: String, - pub title: String, - pub description: String, - #[serde(default)] - pub role: String, - #[serde(default)] - pub owner: String, - #[serde(default = "datetime_now")] - pub created: DateTime, - #[serde(default)] - pub panels: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub layouts: Option>, - pub variables: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Layout { - pub x: i64, - pub y: i64, - pub w: i64, - pub h: i64, - pub i: i64, - pub panel_id: String, - #[serde(rename = "static")] - pub is_static: bool, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Panel { - pub id: String, - #[serde(rename = "type")] - pub typ: String, - pub fields: PanelFields, - pub config: PanelConfig, - pub query: String, - #[serde(default)] - pub query_type: String, - pub custom_query: bool, - } - - #[derive(Deserialize)] - pub struct PanelFields { - pub stream: String, - pub stream_type: StreamType, - pub x: Vec, - pub y: Vec, - pub filter: Vec, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct AxisItem { - pub label: String, - pub alias: String, - pub column: String, - pub color: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub aggregation_function: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "lowercase")] - pub enum AggregationFunc { - Count, - #[serde(rename = "count-distinct")] - CountDistinct, - Histogram, - Sum, - Min, - Max, - Avg, - Median, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct PanelFilter { - #[serde(rename = "type")] - pub typ: String, - pub values: Vec, - pub column: String, - pub operator: Option, - pub value: Option, - } - - #[derive(Deserialize)] - pub struct PanelConfig { - title: String, - description: String, - show_legends: bool, - legends_position: Option, - promql_legend: Option, - #[serde(skip_serializing_if = "Option::is_none")] - unit: Option, - #[serde(skip_serializing_if = "Option::is_none")] - unit_custom: Option, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Variables { - pub list: Vec, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct VariableList { - #[serde(rename = "type")] - pub type_field: String, - pub name: String, - pub label: String, - #[serde(rename = "query_data")] - pub query_data: Option, - pub value: Option, - pub options: Option>, - } - - #[derive(Default, Deserialize)] - pub struct QueryData { - pub stream_type: StreamType, - pub stream: String, - pub field: String, - pub max_record_size: Option, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct CustomFieldsOption { - pub label: String, - pub value: String, - } -} - -/// Representation of the v2 dashboard inner data schema at the time this -/// migration executes. -#[allow(dead_code)] -mod v2 { - use chrono::{DateTime, FixedOffset}; - use serde::Deserialize; - - use super::{datetime_now, stream::StreamType}; - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Dashboard { - version: i32, - #[serde(default)] - pub dashboard_id: String, - pub title: String, - pub description: String, - #[serde(default)] - pub role: String, - #[serde(default)] - pub owner: String, - #[serde(default = "datetime_now")] - pub created: DateTime, - #[serde(default)] - pub panels: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub variables: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Layout { - pub x: i64, - pub y: i64, - pub w: i64, - pub h: i64, - pub i: i64, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Panel { - pub id: String, - #[serde(rename = "type")] - pub typ: String, - pub title: String, - pub description: String, - pub config: PanelConfig, - #[serde(default)] - pub query_type: String, - pub queries: Vec, - pub layout: Layout, - } - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Query { - pub query: String, - pub custom_query: bool, - pub fields: PanelFields, - pub config: QueryConfig, - } - - #[derive(Deserialize)] - pub struct PanelFields { - pub stream: String, - pub stream_type: StreamType, - pub x: Vec, - pub y: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub z: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub latitude: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub longitude: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub weight: Option, - pub filter: Vec, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct AxisItem { - pub label: String, - pub alias: String, - pub column: String, - pub color: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub aggregation_function: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub sort_by: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub args: Option>, - } - - #[derive(Deserialize)] - pub struct AxisArg { - value: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "lowercase")] - pub enum AggregationFunc { - Count, - #[serde(rename = "count-distinct")] - CountDistinct, - Histogram, - Sum, - Min, - Max, - Avg, - Median, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct PanelFilter { - #[serde(rename = "type")] - pub typ: String, - pub values: Vec, - pub column: String, - pub operator: Option, - pub value: Option, - } - - #[derive(Deserialize)] - pub struct PanelConfig { - show_legends: bool, - legends_position: Option, - #[serde(skip_serializing_if = "Option::is_none")] - unit: Option, - #[serde(skip_serializing_if = "Option::is_none")] - unit_custom: Option, - #[serde(skip_serializing_if = "Option::is_none")] - decimals: Option, - #[serde(skip_serializing_if = "Option::is_none")] - axis_width: Option, - #[serde(skip_serializing_if = "Option::is_none")] - axis_border_show: Option, - #[serde(skip_serializing_if = "Option::is_none")] - legend_width: Option, - base_map: Option, - map_view: Option, - } - - #[derive(Deserialize)] - pub struct QueryConfig { - promql_legend: String, - #[serde(skip_serializing_if = "Option::is_none")] - layer_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] - weight_fixed: Option, - #[serde(skip_serializing_if = "Option::is_none")] - limit: Option, - #[serde(skip_serializing_if = "Option::is_none")] - min: Option, - #[serde(skip_serializing_if = "Option::is_none")] - max: Option, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Variables { - pub list: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub show_dynamic_filters: Option, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct VariableList { - #[serde(rename = "type")] - pub type_field: String, - pub name: String, - pub label: String, - #[serde(rename = "query_data")] - pub query_data: Option, - pub value: Option, - pub options: Option>, - } - - #[derive(Default, Deserialize)] - pub struct QueryData { - pub stream_type: StreamType, - pub stream: String, - pub field: String, - pub max_record_size: Option, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct CustomFieldsOption { - pub label: String, - pub value: String, - } - - #[derive(Deserialize)] - pub struct BaseMap { - #[serde(rename = "type")] - pub type_field: Option, - } - - #[derive(Deserialize)] - pub struct MapView { - pub zoom: f64, - pub lat: f64, - pub lng: f64, - } - - #[derive(Deserialize)] - pub struct LegendWidth { - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub unit: Option, - } -} - -/// Representation of the v3 dashboard inner data schema at the time this -/// migration executes. -#[allow(dead_code)] -mod v3 { - use chrono::{DateTime, FixedOffset}; - use serde::Deserialize; - - use super::{datetime_now, stream::StreamType}; - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Dashboard { - version: i32, - #[serde(default)] - pub dashboard_id: String, - pub title: String, - pub description: String, - #[serde(default)] - pub role: String, - #[serde(default)] - pub owner: String, - #[serde(default = "datetime_now")] - pub created: DateTime, - #[serde(default)] - pub tabs: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub variables: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub default_datetime_duration: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Layout { - pub x: i64, - pub y: i64, - pub w: i64, - pub h: i64, - pub i: i64, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Tab { - pub tab_id: String, - pub name: String, - #[serde(default)] - pub panels: Vec, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Panel { - pub id: String, - #[serde(rename = "type")] - pub typ: String, - pub title: String, - pub description: String, - pub config: PanelConfig, - #[serde(default)] - pub query_type: String, - pub queries: Vec, - pub layout: Layout, - #[serde(skip_serializing_if = "Option::is_none")] - pub html_content: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub markdown_content: Option, - } - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Query { - pub query: Option, - pub custom_query: bool, - pub fields: PanelFields, - pub config: QueryConfig, - } - - #[derive(Deserialize)] - pub struct PanelFields { - pub stream: String, - pub stream_type: StreamType, - pub x: Vec, - pub y: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub z: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub latitude: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub longitude: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub weight: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub target: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option, - pub filter: Vec, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct AxisItem { - pub label: String, - pub alias: String, - pub column: String, - pub color: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub aggregation_function: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub sort_by: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub args: Option>, - } - - #[derive(Deserialize)] - pub struct AxisArg { - value: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "lowercase")] - pub enum AggregationFunc { - Count, - #[serde(rename = "count-distinct")] - CountDistinct, - Histogram, - Sum, - Min, - Max, - Avg, - Median, - P50, - P90, - P95, - P99, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct PanelFilter { - #[serde(rename = "type")] - pub typ: String, - pub values: Vec, - pub column: String, - pub operator: Option, - pub value: Option, - } - - #[derive(Deserialize)] - pub struct PanelConfig { - show_legends: bool, - legends_position: Option, - #[serde(skip_serializing_if = "Option::is_none")] - unit: Option, - #[serde(skip_serializing_if = "Option::is_none")] - unit_custom: Option, - #[serde(skip_serializing_if = "Option::is_none")] - decimals: Option, - #[serde(skip_serializing_if = "Option::is_none")] - axis_width: Option, - #[serde(skip_serializing_if = "Option::is_none")] - axis_border_show: Option, - #[serde(skip_serializing_if = "Option::is_none")] - legend_width: Option, - base_map: Option, - map_view: Option, - #[serde(skip_serializing_if = "Option::is_none")] - map_symbol_style: Option, - #[serde(skip_serializing_if = "Option::is_none")] - drilldown: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - connect_nulls: Option, - #[serde(skip_serializing_if = "Option::is_none")] - no_value_replacement: Option, - #[serde(skip_serializing_if = "Option::is_none")] - wrap_table_cells: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct DrillDown { - #[serde(skip_serializing_if = "Option::is_none")] - name: Option, - #[serde(skip_serializing_if = "Option::is_none", rename = "type")] - type_field: Option, - #[serde(skip_serializing_if = "Option::is_none")] - target_blank: Option, - #[serde(skip_serializing_if = "Option::is_none")] - find_by: Option, - #[serde(skip_serializing_if = "Option::is_none")] - data: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct DrillDownData { - #[serde(skip_serializing_if = "Option::is_none")] - url: Option, - #[serde(skip_serializing_if = "Option::is_none")] - folder: Option, - #[serde(skip_serializing_if = "Option::is_none")] - dashboard: Option, - #[serde(skip_serializing_if = "Option::is_none")] - tab: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pass_all_variables: Option, - #[serde(skip_serializing_if = "Option::is_none")] - variables: Option>, - } - - #[derive(Deserialize)] - pub struct DrillDownVariables { - name: Option, - value: Option, - } - - #[derive(Deserialize)] - pub struct QueryConfig { - promql_legend: String, - #[serde(skip_serializing_if = "Option::is_none")] - layer_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] - weight_fixed: Option, - #[serde(skip_serializing_if = "Option::is_none")] - limit: Option, - #[serde(skip_serializing_if = "Option::is_none")] - min: Option, - #[serde(skip_serializing_if = "Option::is_none")] - max: Option, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Variables { - pub list: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub show_dynamic_filters: Option, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct DateTimeOptions { - #[serde(rename = "type")] - pub typee: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub relative_time_period: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub start_time: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub end_time: Option, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct VariableList { - #[serde(rename = "type")] - pub type_field: String, - pub name: String, - pub label: String, - #[serde(rename = "query_data")] - pub query_data: Option, - pub value: Option, - pub options: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub multi_select: Option, - } - - #[derive(Default, Deserialize)] - pub struct QueryData { - pub stream_type: StreamType, - pub stream: String, - pub field: String, - pub max_record_size: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub filter: Option>, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Filters { - pub name: Option, - pub operator: Option, - pub value: String, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct CustomFieldsOption { - pub label: String, - pub value: String, - } - - #[derive(Deserialize)] - pub struct BaseMap { - #[serde(rename = "type")] - pub type_field: Option, - } - - #[derive(Deserialize)] - pub struct MapView { - pub zoom: f64, - pub lat: f64, - pub lng: f64, - } - - #[derive(Deserialize)] - pub struct MapSymbolStyle { - pub size: String, - pub size_by_value: Option, - pub size_fixed: f64, - } - - #[derive(Deserialize)] - pub struct SizeByValue { - pub min: f64, - pub max: f64, - } - - #[derive(Deserialize)] - pub struct LegendWidth { - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub unit: Option, - } -} - -/// Representation of the v4 dashboard inner data schema at the time this -/// migration executes. -#[allow(dead_code)] -mod v4 { - use chrono::{DateTime, FixedOffset}; - use serde::Deserialize; - - use super::{datetime_now, stream::StreamType}; - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Dashboard { - version: i32, - #[serde(default)] - pub dashboard_id: String, - pub title: String, - pub description: String, - #[serde(default)] - pub role: String, - #[serde(default)] - pub owner: String, - #[serde(default = "datetime_now")] - pub created: DateTime, - #[serde(default)] - pub tabs: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub variables: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub default_datetime_duration: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Layout { - pub x: i64, - pub y: i64, - pub w: i64, - pub h: i64, - pub i: i64, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Tab { - pub tab_id: String, - pub name: String, - #[serde(default)] - pub panels: Vec, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Panel { - pub id: String, - #[serde(rename = "type")] - pub typ: String, - pub title: String, - pub description: String, - pub config: PanelConfig, - #[serde(default)] - pub query_type: String, - pub queries: Vec, - pub layout: Layout, - #[serde(skip_serializing_if = "Option::is_none")] - pub html_content: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub markdown_content: Option, - } - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Query { - pub query: Option, - pub vrl_function_query: Option, - pub custom_query: bool, - pub fields: PanelFields, - pub config: QueryConfig, - } - - #[derive(Deserialize)] - pub struct PanelFields { - pub stream: String, - pub stream_type: StreamType, - pub x: Vec, - pub y: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub z: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub breakdown: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub latitude: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub longitude: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub weight: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub target: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option, - pub filter: Vec, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct AxisItem { - pub label: String, - pub alias: String, - pub column: String, - pub color: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub aggregation_function: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub sort_by: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub args: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub is_derived: Option, - } - - #[derive(Deserialize)] - pub struct AxisArg { - value: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "lowercase")] - pub enum AggregationFunc { - Count, - #[serde(rename = "count-distinct")] - CountDistinct, - Histogram, - Sum, - Min, - Max, - Avg, - Median, - P50, - P90, - P95, - P99, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct PanelFilter { - #[serde(rename = "type")] - pub typ: String, - pub values: Vec, - pub column: String, - pub operator: Option, - pub value: Option, - } - - #[derive(Deserialize)] - pub struct PanelConfig { - show_legends: bool, - legends_position: Option, - #[serde(skip_serializing_if = "Option::is_none")] - unit: Option, - #[serde(skip_serializing_if = "Option::is_none")] - unit_custom: Option, - #[serde(skip_serializing_if = "Option::is_none")] - decimals: Option, - #[serde(skip_serializing_if = "Option::is_none")] - top_results: Option, - #[serde(skip_serializing_if = "Option::is_none")] - top_results_others: Option, - #[serde(skip_serializing_if = "Option::is_none")] - axis_width: Option, - #[serde(skip_serializing_if = "Option::is_none")] - axis_border_show: Option, - #[serde(skip_serializing_if = "Option::is_none")] - legend_width: Option, - base_map: Option, - map_view: Option, - #[serde(skip_serializing_if = "Option::is_none")] - map_symbol_style: Option, - #[serde(skip_serializing_if = "Option::is_none")] - drilldown: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - mark_line: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - connect_nulls: Option, - #[serde(skip_serializing_if = "Option::is_none")] - no_value_replacement: Option, - #[serde(skip_serializing_if = "Option::is_none")] - wrap_table_cells: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct DrillDown { - #[serde(skip_serializing_if = "Option::is_none")] - name: Option, - #[serde(skip_serializing_if = "Option::is_none", rename = "type")] - type_field: Option, - #[serde(skip_serializing_if = "Option::is_none")] - target_blank: Option, - #[serde(skip_serializing_if = "Option::is_none")] - find_by: Option, - #[serde(skip_serializing_if = "Option::is_none")] - data: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct MarkLine { - #[serde(skip_serializing_if = "Option::is_none")] - name: Option, - #[serde(skip_serializing_if = "Option::is_none", rename = "type")] - typee: Option, - #[serde(skip_serializing_if = "Option::is_none")] - value: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct DrillDownData { - #[serde(skip_serializing_if = "Option::is_none")] - url: Option, - #[serde(skip_serializing_if = "Option::is_none")] - folder: Option, - #[serde(skip_serializing_if = "Option::is_none")] - dashboard: Option, - #[serde(skip_serializing_if = "Option::is_none")] - tab: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pass_all_variables: Option, - #[serde(skip_serializing_if = "Option::is_none")] - variables: Option>, - } - - #[derive(Deserialize)] - pub struct DrillDownVariables { - name: Option, - value: Option, - } - - #[derive(Deserialize)] - pub struct QueryConfig { - promql_legend: String, - #[serde(skip_serializing_if = "Option::is_none")] - layer_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] - weight_fixed: Option, - #[serde(skip_serializing_if = "Option::is_none")] - limit: Option, - #[serde(skip_serializing_if = "Option::is_none")] - min: Option, - #[serde(skip_serializing_if = "Option::is_none")] - max: Option, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Variables { - pub list: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub show_dynamic_filters: Option, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct DateTimeOptions { - #[serde(rename = "type")] - pub typee: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub relative_time_period: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub start_time: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub end_time: Option, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct VariableList { - #[serde(rename = "type")] - pub type_field: String, - pub name: String, - pub label: String, - #[serde(rename = "query_data")] - pub query_data: Option, - pub value: Option, - pub options: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub multi_select: Option, - } - - #[derive(Default, Deserialize)] - pub struct QueryData { - pub stream_type: StreamType, - pub stream: String, - pub field: String, - pub max_record_size: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub filter: Option>, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Filters { - pub name: Option, - pub operator: Option, - pub value: String, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct CustomFieldsOption { - pub label: String, - pub value: String, - } - - #[derive(Deserialize)] - pub struct BaseMap { - #[serde(rename = "type")] - pub type_field: Option, - } - - #[derive(Deserialize)] - pub struct MapView { - pub zoom: f64, - pub lat: f64, - pub lng: f64, - } - - #[derive(Deserialize)] - pub struct MapSymbolStyle { - pub size: String, - pub size_by_value: Option, - pub size_fixed: f64, - } - - #[derive(Deserialize)] - pub struct SizeByValue { - pub min: f64, - pub max: f64, - } - - #[derive(Deserialize)] - pub struct LegendWidth { - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub unit: Option, - } -} - -/// Representation of the v5 dashboard inner data schema at the time this -/// migration executes. -#[allow(dead_code)] -mod v5 { - use chrono::{DateTime, FixedOffset}; - use serde::Deserialize; - - use super::{datetime_now, stream::StreamType}; - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Dashboard { - version: i32, - #[serde(default)] - pub dashboard_id: String, - pub title: String, - pub description: String, - #[serde(default)] - pub role: String, - #[serde(default)] - pub owner: String, - #[serde(default = "datetime_now")] - pub created: DateTime, - #[serde(default)] - pub tabs: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub variables: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub default_datetime_duration: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Layout { - pub x: i64, - pub y: i64, - pub w: i64, - pub h: i64, - pub i: i64, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Tab { - pub tab_id: String, - pub name: String, - #[serde(default)] - pub panels: Vec, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Panel { - pub id: String, - #[serde(rename = "type")] - pub typ: String, - pub title: String, - pub description: String, - pub config: PanelConfig, - #[serde(default)] - pub query_type: String, - pub queries: Vec, - pub layout: Layout, - #[serde(skip_serializing_if = "Option::is_none")] - pub html_content: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub markdown_content: Option, - } - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Query { - pub query: Option, - pub vrl_function_query: Option, - pub custom_query: bool, - pub fields: PanelFields, - pub config: QueryConfig, - } - - #[derive(Deserialize)] - pub struct PanelFields { - pub stream: String, - pub stream_type: StreamType, - pub x: Vec, - pub y: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub z: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub breakdown: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub latitude: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub longitude: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub weight: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub value_for_maps: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub target: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option, - pub filter: PanelFilter, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct AxisItem { - pub label: String, - pub alias: String, - pub column: String, - pub color: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub aggregation_function: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub sort_by: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub args: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub is_derived: Option, - } - - #[derive(Deserialize)] - pub struct AxisArg { - value: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "lowercase")] - pub enum AggregationFunc { - Count, - #[serde(rename = "count-distinct")] - CountDistinct, - Histogram, - Sum, - Min, - Max, - Avg, - Median, - P50, - P90, - P95, - P99, - } - - #[derive(Deserialize)] - #[serde(untagged, rename_all = "camelCase")] - pub enum PanelFilter { - #[serde(rename = "condition")] - Condition(FilterCondition), - #[serde(rename = "group")] - Group(GroupType), - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct GroupType { - pub filter_type: String, - pub logical_operator: String, - pub conditions: Vec, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct FilterCondition { - #[serde(rename = "type")] - pub typ: String, - pub values: Vec, - pub column: String, - pub operator: Option, - pub value: Option, - pub logical_operator: String, - pub filter_type: String, - } - - #[derive(Deserialize)] - pub struct PanelConfig { - show_legends: bool, - legends_position: Option, - #[serde(skip_serializing_if = "Option::is_none")] - unit: Option, - #[serde(skip_serializing_if = "Option::is_none")] - unit_custom: Option, - #[serde(skip_serializing_if = "Option::is_none")] - decimals: Option, - #[serde(skip_serializing_if = "Option::is_none")] - line_thickness: Option, - #[serde(skip_serializing_if = "Option::is_none")] - top_results: Option, - #[serde(skip_serializing_if = "Option::is_none")] - top_results_others: Option, - #[serde(skip_serializing_if = "Option::is_none")] - axis_width: Option, - #[serde(skip_serializing_if = "Option::is_none")] - axis_border_show: Option, - #[serde(skip_serializing_if = "Option::is_none")] - label_option: Option, - #[serde(skip_serializing_if = "Option::is_none")] - show_symbol: Option, - #[serde(skip_serializing_if = "Option::is_none")] - line_interpolation: Option, - #[serde(skip_serializing_if = "Option::is_none")] - legend_width: Option, - base_map: Option, - #[serde(skip_serializing_if = "Option::is_none")] - map_type: Option, - map_view: Option, - #[serde(skip_serializing_if = "Option::is_none")] - map_symbol_style: Option, - #[serde(skip_serializing_if = "Option::is_none")] - drilldown: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - mark_line: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - override_config: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - connect_nulls: Option, - #[serde(skip_serializing_if = "Option::is_none")] - no_value_replacement: Option, - #[serde(skip_serializing_if = "Option::is_none")] - wrap_table_cells: Option, - #[serde(skip_serializing_if = "Option::is_none")] - table_transpose: Option, - #[serde(skip_serializing_if = "Option::is_none")] - table_dynamic_columns: Option, - #[serde(skip_serializing_if = "Option::is_none")] - mappings: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - color: Option, - #[serde(skip_serializing_if = "Option::is_none")] - trellis: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct ColorCfg { - #[serde(skip_serializing_if = "Option::is_none")] - mode: Option, - #[serde(skip_serializing_if = "Option::is_none")] - fixed_color: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - series_by: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Mapping { - #[serde(skip_serializing_if = "Option::is_none", rename = "type")] - typee: Option, - #[serde(skip_serializing_if = "Option::is_none")] - value: Option, - #[serde(skip_serializing_if = "Option::is_none")] - from: Option, - #[serde(skip_serializing_if = "Option::is_none")] - to: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pattern: Option, - #[serde(skip_serializing_if = "Option::is_none", rename = "match")] - matchh: Option, - #[serde(skip_serializing_if = "Option::is_none")] - color: Option, - #[serde(skip_serializing_if = "Option::is_none")] - text: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct DrillDown { - #[serde(skip_serializing_if = "Option::is_none")] - name: Option, - #[serde(skip_serializing_if = "Option::is_none", rename = "type")] - type_field: Option, - #[serde(skip_serializing_if = "Option::is_none")] - target_blank: Option, - #[serde(skip_serializing_if = "Option::is_none")] - find_by: Option, - #[serde(skip_serializing_if = "Option::is_none")] - data: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct MarkLine { - #[serde(skip_serializing_if = "Option::is_none")] - name: Option, - #[serde(skip_serializing_if = "Option::is_none", rename = "type")] - typee: Option, - #[serde(skip_serializing_if = "Option::is_none")] - value: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct OverrideConfig { - #[serde(skip_serializing_if = "Option::is_none")] - field: Option, - #[serde(skip_serializing_if = "Option::is_none")] - config: Option>, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Field { - match_by: String, - value: String, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Config { - #[serde(rename = "type")] - typee: String, - #[serde(skip_serializing_if = "Option::is_none")] - value: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Value { - unit: String, - custom_unit: String, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct DrillDownData { - #[serde(skip_serializing_if = "Option::is_none")] - url: Option, - #[serde(skip_serializing_if = "Option::is_none")] - folder: Option, - #[serde(skip_serializing_if = "Option::is_none")] - dashboard: Option, - #[serde(skip_serializing_if = "Option::is_none")] - tab: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pass_all_variables: Option, - #[serde(skip_serializing_if = "Option::is_none")] - variables: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - logs_mode: Option, - #[serde(skip_serializing_if = "Option::is_none")] - logs_query: Option, - } - - #[derive(Deserialize)] - pub struct DrillDownVariables { - name: Option, - value: Option, - } - - #[derive(Deserialize)] - pub struct QueryConfig { - promql_legend: String, - #[serde(skip_serializing_if = "Option::is_none")] - layer_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] - weight_fixed: Option, - #[serde(skip_serializing_if = "Option::is_none")] - limit: Option, - #[serde(skip_serializing_if = "Option::is_none")] - min: Option, - #[serde(skip_serializing_if = "Option::is_none")] - max: Option, - #[serde(skip_serializing_if = "Option::is_none")] - time_shift: Option>, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct TimeShift { - #[serde(skip_serializing_if = "Option::is_none")] - off_set: Option, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Variables { - pub list: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub show_dynamic_filters: Option, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct DateTimeOptions { - #[serde(rename = "type")] - pub typee: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub relative_time_period: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub start_time: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub end_time: Option, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct VariableList { - #[serde(rename = "type")] - pub type_field: String, - pub name: String, - pub label: String, - #[serde(rename = "query_data")] - pub query_data: Option, - pub value: Option, - pub options: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub multi_select: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub hide_on_dashboard: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub select_all_value_for_multi_select: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub custom_multi_select_value: Option>, - } - - #[derive(Default, Deserialize)] - pub struct QueryData { - pub stream_type: StreamType, - pub stream: String, - pub field: String, - pub max_record_size: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub filter: Option>, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Filters { - pub name: Option, - pub operator: Option, - pub value: String, - } - - #[derive(Default, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct CustomFieldsOption { - pub label: String, - pub value: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub selected: Option, - } - - #[derive(Deserialize)] - pub struct BaseMap { - #[serde(rename = "type")] - pub type_field: Option, - } - - #[derive(Deserialize)] - pub struct MapType { - #[serde(rename = "type")] - pub type_field: Option, - } - - #[derive(Deserialize)] - pub struct MapView { - pub zoom: f64, - pub lat: f64, - pub lng: f64, - } - - #[derive(Deserialize)] - pub struct Trellis { - pub layout: Option, - pub num_of_columns: i64, - } - - #[derive(Deserialize)] - pub struct MapSymbolStyle { - pub size: String, - pub size_by_value: Option, - pub size_fixed: f64, - } - - #[derive(Deserialize)] - pub struct SizeByValue { - pub min: f64, - pub max: f64, - } - - #[derive(Deserialize)] - pub struct LegendWidth { - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub unit: Option, - } - - #[derive(Deserialize)] - pub struct LabelOption { - #[serde(skip_serializing_if = "Option::is_none")] - pub position: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub rotate: Option, - } - - #[derive(Deserialize)] - #[serde(rename_all = "lowercase")] - pub enum LineInterpolation { - Smooth, - Linear, - #[serde(rename = "step-start")] - StepStart, - #[serde(rename = "step-end")] - StepEnd, - #[serde(rename = "step-middle")] - StepMiddle, - } - - #[derive(Deserialize)] - #[serde(rename_all = "lowercase")] - pub enum LabelPosition { - Top, - Inside, - InsideTop, - InsideBottom, - } -} - -mod stream { - use serde::Deserialize; - - #[derive(Clone, Debug, Default, Deserialize)] - #[serde(rename_all = "lowercase")] - pub enum StreamType { - #[default] - Logs, - Metrics, - Traces, - #[serde(rename = "enrichment_tables")] - EnrichmentTables, - #[serde(rename = "file_list")] - Filelist, - Metadata, - Index, - } -} - -fn datetime_now() -> DateTime { - chrono::Utc::now().with_timezone(&FixedOffset::east_opt(0).expect( - "BUG", // This can't possibly fail. Can it? - )) -}