Dashboards
Overview
Dashboards group multiple metric visualizations into structured views. Each dashboard belongs to a specific environment (environment_id) and contains views, sections, and widgets with visualization configurations and field mappings.
Key pieces:
- Pydantic data models for dashboards, views, sections, widgets, and visualization configs
- A single-table ORM with a JSON
configcolumn that stores nested dashboard configuration - CRUD services that read/write dashboards using
CortexStorage().get_session() - An execution service that loads a dashboard and produces standardized chart data
- A mapping layer that validates and transforms metric results for different visualization types
Core Pydantic models
Dashboard:
class Dashboard(TSModel):
"""
Core dashboard definition that combines semantic metrics into a cohesive view.
Each dashboard is tied to a specific environment and contains multiple views.
Frontend handles all layout, theming, and visual presentation.
"""
id: UUID = Field(default_factory=uuid4)
alias: Optional[str] = None
environment_id: UUID
name: str
description: Optional[str] = None
type: DashboardType
views: List["DashboardView"]
default_view: str
tags: Optional[List[str]] = None
created_by: UUID
created_at: datetime = Field(default_factory=lambda: datetime.now(pytz.UTC))
updated_at: datetime = Field(default_factory=lambda: datetime.now(pytz.UTC))
last_viewed_at: Optional[datetime] = None
View and Section:
class DashboardView(TSModel):
"""A specific view within a dashboard."""
alias: str
title: str
description: Optional[str] = None
sections: List["DashboardSection"]
context_id: Optional[str] = None
layout: Optional[DashboardLayout] = None
created_at: datetime = Field(default_factory=lambda: datetime.now(pytz.UTC))
updated_at: datetime = Field(default_factory=lambda: datetime.now(pytz.UTC))
class DashboardSection(TSModel):
"""Logical grouping of related widgets within a view."""
alias: str
title: Optional[str] = None
description: Optional[str] = None
position: int
widgets: List["DashboardWidget"]
Widgets and visualization configuration:
class VisualizationConfig(TSModel):
type: VisualizationType
data_mapping: DataMapping
chart_config: Optional[ChartConfig] = None
table_config: Optional[TableConfig] = None
single_value_config: Optional[SingleValueConfig] = None
gauge_config: Optional[GaugeConfig] = None
show_legend: bool = True
show_grid: bool = True
show_axes_labels: bool = True
color_scheme: Optional[ColorScheme] = None
custom_colors: Optional[List[str]] = None
class DashboardWidget(TSModel):
"""Individual metric widget with visualization."""
alias: str
section_alias: str
metric_id: UUID
position: int
grid_config: WidgetGridConfig
title: str
description: Optional[str] = None
visualization: VisualizationConfig
metric_overrides: Optional[MetricExecutionOverrides] = None
Common visualization configs and helpers:
class WidgetGridConfig(TSModel):
columns: int = 1
rows: int = 1
min_columns: Optional[int] = None
min_rows: Optional[int] = None
class MetricExecutionOverrides(TSModel):
context_id: Optional[str] = None
filters: Optional[Dict[str, Any]] = None
parameters: Optional[Dict[str, Any]] = None
limit: Optional[int] = None
class ChartConfig(TSModel):
show_points: bool = True
line_width: int = 2
bar_width: Optional[float] = None
stack_bars: bool = False
smooth_lines: bool = False
area_stacking_type: Optional[Literal['normal', 'gradient']] = None
class TableConfig(TSModel):
show_header: bool = True
sortable: bool = True
pagination: bool = True
page_size: int = 10
searchable: bool = False
class SingleValueConfig(TSModel):
number_format: NumberFormat
prefix: Optional[str] = None
suffix: Optional[str] = None
show_comparison: bool = True
comparison_config: Optional[ComparisonConfig] = None
show_trend: bool = True
trend_period: Optional[str] = "previous_period"
show_sparkline: bool = False
show_title: bool = True
show_description: bool = False
compact_mode: bool = False
selection_mode: ValueSelectionMode = ValueSelectionMode.FIRST
selection_config: Optional[ValueSelectionConfig] = None
class GaugeConfig(TSModel):
min_value: float = 0
max_value: float = 100
target_value: Optional[float] = None
color_ranges: Optional[List[Dict[str, Any]]] = None
show_value: bool = True
show_target: bool = True
gauge_type: str = "arc"
thickness: int = 10
selection_mode: ValueSelectionMode = ValueSelectionMode.FIRST
selection_config: Optional[ValueSelectionConfig] = None
Data mapping layer
Widgets use VisualizationConfig.data_mapping to validate the fields expected by each visualization and to assist with transforming raw metric rows into chart-friendly shapes. The shared base mapping class:
class DataMapping(TSModel):
x_axis: Optional[FieldMapping] = None
y_axes: Optional[List[FieldMapping]] = None
value_field: Optional[FieldMapping] = None
category_field: Optional[FieldMapping] = None
series_field: Optional[FieldMapping] = None
columns: Optional[List[ColumnMapping]] = None
def get_all_fields(self) -> List[str]:
...
def validate_against_result(self, result_columns: List[str]) -> None:
...
Supported visualizations are wired through a factory registry:
class MappingFactory:
MAPPING_REGISTRY: Dict[VisualizationType, Type[VisualizationMapping]] = {
VisualizationType.SINGLE_VALUE: SingleValueMapping,
VisualizationType.BAR_CHART: ChartMapping,
VisualizationType.LINE_CHART: ChartMapping,
VisualizationType.AREA_CHART: ChartMapping,
VisualizationType.PIE_CHART: ChartMapping,
VisualizationType.DONUT_CHART: ChartMapping,
VisualizationType.SCATTER_PLOT: ChartMapping,
VisualizationType.TABLE: TableMapping,
VisualizationType.GAUGE: GaugeMapping,
}
Validation rules and transformations per type are implemented in mapping modules:
modules/single_value.py: usesx_axisas the value; supports selection and formattingmodules/chart.py: supports x/y series or category/value shapes; multiple series and stacking hintsmodules/table.py: requires explicit column mappings and outputs table payloadsmodules/gauge.py: usesx_axisas value; supports selection strategies and percentage within range
Persistence model (ORM)
Dashboards are stored in a single table with JSON configuration for nested structures:
class DashboardORM(BaseDBModel):
__tablename__ = "dashboards"
id = mapped_column(UUID, primary_key=True, index=True)
environment_id = mapped_column(UUID, nullable=False, index=True)
name = mapped_column(String(255), nullable=False, index=True)
description = mapped_column(Text, nullable=True)
type = mapped_column(String(50), nullable=False)
default_view = mapped_column(String(255), nullable=False)
tags = mapped_column(JSON, nullable=True)
created_by = mapped_column(UUID, nullable=False)
created_at = mapped_column(DateTime, default=datetime.now(pytz.UTC))
updated_at = mapped_column(DateTime, default=datetime.now(pytz.UTC), onupdate=datetime.now(pytz.UTC))
last_viewed_at = mapped_column(DateTime, nullable=True)
config = mapped_column(JSON, nullable=False, default=dict)
CRUD utilities load/store dashboards and materialize the Pydantic model by merging config back into flat fields as needed:
class DashboardCRUD(TSModel):
@staticmethod
def get_dashboard_by_id(dashboard_id: UUID) -> Optional[Dashboard]:
db_session = CortexStorage().get_session()
try:
db_dashboard = db_session.query(DashboardORM).filter(
DashboardORM.id == dashboard_id
).first()
if db_dashboard is None:
return None
dashboard_dict = db_dashboard.__dict__.copy()
config = dashboard_dict.get('config') or {}
for key in ('views',):
if key in config:
dashboard_dict[key] = config[key]
return Dashboard.model_validate(dashboard_dict, from_attributes=True)
finally:
db_session.close()
Execution and output format
The execution service loads a dashboard, resolves the target view by alias, executes each widget’s metric, and returns a standardized chart payload:
class DashboardExecutionService(TSModel):
@staticmethod
def execute_dashboard(dashboard_id: UUID, view_alias: Optional[str] = None) -> DashboardExecutionResult:
start_time = time.time()
try:
dashboard = DashboardCRUD.get_dashboard_by_id(dashboard_id)
if dashboard is None:
raise DashboardDoesNotExistError(dashboard_id)
target_view_alias = view_alias or dashboard.default_view
target_view = None
for view in dashboard.views:
if view.alias == target_view_alias:
target_view = view
break
if target_view is None:
raise DashboardViewDoesNotExistError(target_view_alias)
view_result = DashboardExecutionService.execute_view(dashboard_id, target_view_alias)
total_time = (time.time() - start_time) * 1000
return DashboardExecutionResult(
dashboard_id=dashboard_id,
view_id=target_view_alias,
view_execution=view_result,
total_execution_time_ms=total_time
)
except Exception as e:
raise DashboardExecutionError(dashboard_id, str(e))
Standardized chart data structures are used as the canonical output for all visualizations:
class ChartDataPoint(TSModel):
x: Union[str, int, float]
y: Union[int, float]
label: Optional[str] = None
category: Optional[str] = None
metadata: Optional[Dict[str, Any]] = None
class ChartSeries(TSModel):
name: str
data: List[ChartDataPoint]
type: Optional[str] = None
color: Optional[str] = None
metadata: Optional[Dict[str, Any]] = None
class ProcessedChartData(TSModel):
series: Optional[List[ChartSeries]] = None
categories: Optional[List[CategoryData]] = None
table: Optional[TableData] = None
value: Optional[Union[int, float, str]] = None
totals: Optional[Dict[str, float]] = None
averages: Optional[Dict[str, float]] = None
trends: Optional[List[TrendData]] = None
class StandardChartData(TSModel):
raw: Dict[str, Any]
processed: ProcessedChartData
metadata: ChartMetadata
Supported Visualization Types
Cortex supports 9 visualization types, each optimized for different data patterns and use cases.
Quick Reference
| Visualization | Best For | Data Structure |
|---|---|---|
| Single Value | KPIs, summary stats | Single number |
| Gauge | Progress, performance | Single number with range |
| Bar Chart | Comparisons, rankings | Category + value |
| Line Chart | Trends over time | Time + value |
| Area Chart | Cumulative trends | Time + value |
| Pie / Donut | Proportions, parts | Category + value |
| Scatter Plot | Correlations, patterns | Two numbers (x, y) |
| Box Plot | Distributions, outliers | Category + numerical values |
| Table | Detailed records | Multiple columns |
Single Value (KPI Card)
When to use:
- Displaying a single key metric
- Executive dashboards showing critical KPIs
- Summary statistics that need immediate visibility
Data requirements:
- Single numeric value from metric result
- Works best with aggregations (SUM, COUNT, AVG, etc.)
Field mappings:
x_axis: The value field to display
Example:
{
"visualization": {
"type": "single_value",
"data_mapping": {
"x_axis": {
"field": "total_revenue",
"label": "Total Revenue",
"format": {
"number_format": "currency",
"prefix": "$"
}
}
},
"single_value_config": {
"show_comparison": true,
"show_sparkline": true
}
}
}
Gauge
When to use:
- Showing progress toward a goal
- Displaying percentage completion
- Performance indicators with targets
Data requirements:
- Single numeric value within a min/max range
- Optional target value
Field mappings:
x_axis: The value field- Configure
min_value,max_value, and optionaltarget_value
Example:
{
"visualization": {
"type": "gauge",
"data_mapping": {
"x_axis": {
"field": "completion_rate",
"label": "Project Completion"
}
},
"gauge_config": {
"min_value": 0,
"max_value": 100,
"target_value": 75
}
}
}
Bar Chart
When to use:
- Comparing values across categories
- Ranking items (top customers, products, etc.)
- Showing categorical distributions
Data requirements:
- Categorical x-axis (category)
- Numeric y-axis (value)
Field mappings:
x_axis: Category fieldy_axes: Value field(s) - can have multiple series
Example:
{
"visualization": {
"type": "bar_chart",
"data_mapping": {
"x_axis": {
"field": "product_category",
"label": "Product Category"
},
"y_axes": [{
"field": "revenue",
"label": "Revenue"
}]
},
"chart_config": {
"stack_bars": false,
"bar_width": 0.6
}
}
}
Line Chart
When to use:
- Showing trends over time
- Tracking metrics over continuous periods
- Comparing multiple series over time
Data requirements:
- Time series or continuous x-axis
- Numeric y-axis values
Field mappings:
x_axis: Time or continuous fieldy_axes: Value field(s)- Optional
series_field: For multiple lines
Example:
{
"visualization": {
"type": "line_chart",
"data_mapping": {
"x_axis": {
"field": "date",
"label": "Date"
},
"y_axes": [{
"field": "revenue",
"label": "Revenue"
}]
},
"chart_config": {
"show_points": true,
"line_width": 2
}
}
}
Area Chart
When to use:
- Emphasizing the magnitude of change
- Showing cumulative values
- Volume and cumulative trends
Data requirements:
- Time or continuous x-axis
- Numeric y-axis values
Field mappings:
x_axis: Time fieldy_axes: Value field(s)
Example:
{
"visualization": {
"type": "area_chart",
"data_mapping": {
"x_axis": {
"field": "month",
"label": "Month"
},
"y_axes": [{
"field": "cumulative_revenue",
"label": "Cumulative Revenue"
}]
},
"chart_config": {
"area_stacking_type": "normal"
}
}
}
Pie Chart / Donut Chart
When to use:
- Showing part-to-whole relationships
- Displaying proportions or percentages
- Small categorical breakdowns (5-7 categories max)
Best practices:
- Use when values represent parts of a whole
- Limit to 5-7 categories for readability
- Don't use for trends over time
Data requirements:
- Category field
- Value field (should sum meaningfully)
Field mappings:
category_field: Category namesvalue_field: Values
Example:
{
"visualization": {
"type": "pie_chart",
"data_mapping": {
"category_field": {
"field": "product_category",
"label": "Category"
},
"value_field": {
"field": "revenue",
"label": "Revenue"
}
}
}
}
Scatter Plot
When to use:
- Showing correlation between two variables
- Detecting patterns in data
- Analyzing relationships between metrics
Data requirements:
- Two numerical variables
Field mappings:
x_axis: First numerical variabley_axes: Second numerical variable
Example:
{
"visualization": {
"type": "scatter_plot",
"data_mapping": {
"x_axis": {
"field": "price",
"label": "Price"
},
"y_axes": [{
"field": "quantity_sold",
"label": "Quantity Sold"
}]
}
}
}
Box Plot
When to use:
- Displaying data distribution
- Identifying outliers
- Comparing statistical distributions across categories
- Showing quartiles and medians
Data requirements:
- Categorical x-axis (groups)
- Numerical y-axis (values to analyze)
Field mappings:
x_axis: Category fieldy_axes: Numerical value field
What it shows:
- Min: Minimum value
- Q1: First quartile (25th percentile)
- Median: Middle value (50th percentile)
- Q3: Third quartile (75th percentile)
- Max: Maximum value
- Outliers: Points beyond 1.5 * IQR
Example:
{
"visualization": {
"type": "box_plot",
"data_mapping": {
"x_axis": {
"field": "region",
"label": "Sales Region"
},
"y_axes": [{
"field": "sales_amount",
"label": "Sales Amount"
}]
}
}
}
Table
When to use:
- Showing detailed records
- Displaying multiple attributes
- Tabular data exploration
- Export-ready data presentation
Data requirements:
- Any structured data with columns
- Multiple fields to display
Field mappings:
columns: Array of column definitions
Example:
{
"visualization": {
"type": "table",
"data_mapping": {
"columns": [
{
"field": "customer_name",
"label": "Customer Name",
"width": 200,
"sortable": true
},
{
"field": "order_date",
"label": "Order Date",
"width": 150,
"format": {
"date_format": "YYYY-MM-DD"
}
},
{
"field": "order_total",
"label": "Order Total",
"width": 120,
"format": {
"number_format": "currency"
}
}
]
},
"table_config": {
"sortable": true,
"pagination": true,
"page_size": 25
}
}
}
Notes and best practices
- Always set
environment_idon dashboards to ensure the correct environment scoping. - Use
aliason dashboards and views for stable referencing from the UI. - Visualization configs should include explicit field mappings; mappings validate against metric result columns.
- For tables, provide explicit column mappings in
VisualizationConfig.data_mapping.columns. - Persist and update the entire nested configuration through
DashboardCRUD(JSONconfigfield).