{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://raw.githubusercontent.com/api-evangelist/vertesiahq/main/json-schema/vertesiahq-dashboard-schema.json",
  "title": "Dashboard",
  "description": "Full dashboard with SQL query and Vega-Lite specification.\n\n**New architecture (v2):**\n- `dataSource` field with either SQL or Store Elasticsearch DSL\n- Single `spec` field with complete Vega-Lite spec (vconcat/hconcat for multiple panels)\n- Cross-panel interactivity via Vega selections\n- Legacy top-level `query` fields are treated as a SQL data source\n\n**Legacy architecture (v1, deprecated):**\n- Multiple `queries` with named data sources\n- Multiple `panels` with separate specs and dataSources references\n- `layout` for grid positioning\n- No cross-panel interactivity",
  "x-generated": "2026-10-22",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/vertesia-openapi.json#/components/schemas/Dashboard",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "description": "Unique identifier for the object"
    },
    "edit_revision": {
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991,
      "description": "Monotonic edit revision used to detect concurrent updates."
    },
    "name": {
      "type": "string",
      "description": "Human-readable name or title"
    },
    "description": {
      "type": "string",
      "description": "Optional detailed description of the object"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Tags for organization"
    },
    "updated_by": {
      "type": "string",
      "description": "Identifier of the user who last modified the object"
    },
    "created_by": {
      "type": "string",
      "description": "Identifier of the user who created the object"
    },
    "created_at": {
      "type": "string",
      "description": "ISO timestamp of when the object was created"
    },
    "updated_at": {
      "type": "string",
      "description": "ISO timestamp of when the object was last updated"
    },
    "store_id": {
      "type": "string",
      "description": "Parent data store ID"
    },
    "status": {
      "$ref": "#/$defs/DashboardStatus",
      "description": "Current status"
    },
    "panel_count": {
      "type": "number",
      "description": "Number of panels (deprecated, kept for backwards compat)"
    },
    "query_count": {
      "type": "number",
      "description": "Number of queries (deprecated, kept for backwards compat)"
    },
    "last_rendered_at": {
      "type": "string",
      "description": "Last render timestamp"
    },
    "source": {
      "type": "string",
      "enum": [
        "stored",
        "app"
      ],
      "description": "Source of the dashboard definition. Defaults to stored dashboards."
    },
    "app_name": {
      "type": "string",
      "description": "App name when `source` is `app`."
    },
    "readonly": {
      "type": "boolean",
      "description": "App dashboards are read-only until cloned into a stored dashboard."
    },
    "dataSource": {
      "$ref": "#/$defs/DashboardDataSource",
      "description": "Data source used to populate Vega `data.values`. When omitted, top-level `query` is treated as a SQL data source for backwards compatibility."
    },
    "query": {
      "type": "string",
      "deprecated": true,
      "x-deprecated-message": "Use `dataSource: { kind: 'data_sql', query }` instead.",
      "description": "SQL query that returns all data for the dashboard. Use JOINs, CTEs, or UNION ALL to combine data from multiple tables. Can include {{param_name}} placeholders for dynamic values.\n\nDeprecated: Use `dataSource: { kind: 'data_sql', query }` instead."
    },
    "queryLimit": {
      "type": "number",
      "deprecated": true,
      "x-deprecated-message": "Use `dataSource.queryLimit` instead.",
      "description": "Maximum rows to return from the query (default: 10000).\n\nDeprecated: Use `dataSource.queryLimit` instead."
    },
    "queryParameters": {
      "deprecated": true,
      "x-deprecated-message": "Use `dataSource.queryParameters` instead.",
      "$ref": "#/$defs/StringValueMap",
      "description": "Default values for SQL parameters.\n\nDeprecated: Use `dataSource.queryParameters` instead."
    },
    "spec": {
      "type": "object",
      "additionalProperties": true,
      "description": "Complete Vega-Lite specification for the entire dashboard. Use vconcat/hconcat for multiple panels, params for interactivity. Data is injected at runtime from query results."
    },
    "queries": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/DashboardQuery"
      },
      "deprecated": true,
      "x-deprecated-message": "Use single `query` field instead.\nNamed SQL queries (kept for backwards compatibility).",
      "description": "Deprecated: Use single `query` field instead.\nNamed SQL queries (kept for backwards compatibility)."
    },
    "panels": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/DashboardPanel"
      },
      "deprecated": true,
      "x-deprecated-message": "Use single `spec` field with vconcat/hconcat instead.\nPanel definitions (kept for backwards compatibility).",
      "description": "Deprecated: Use single `spec` field with vconcat/hconcat instead.\nPanel definitions (kept for backwards compatibility)."
    },
    "layout": {
      "deprecated": true,
      "x-deprecated-message": "Layout is now handled within the Vega-Lite spec.\nLayout configuration (kept for backwards compatibility).",
      "$ref": "#/$defs/DashboardLayout",
      "description": "Deprecated: Layout is now handled within the Vega-Lite spec.\nLayout configuration (kept for backwards compatibility)."
    },
    "last_render_url": {
      "type": "string",
      "description": "URL of last rendered image"
    }
  },
  "required": [
    "id",
    "edit_revision",
    "name",
    "tags",
    "updated_by",
    "created_by",
    "created_at",
    "updated_at",
    "store_id",
    "status",
    "panel_count",
    "query_count",
    "queries",
    "panels",
    "layout"
  ],
  "additionalProperties": false,
  "$defs": {
    "DashboardDataSource": {
      "oneOf": [
        {
          "$ref": "#/$defs/DashboardSqlDataSource"
        },
        {
          "$ref": "#/$defs/DashboardStoreElasticsearchDataSource"
        }
      ],
      "type": "object",
      "required": [
        "kind"
      ],
      "description": "Data source for a Vega dashboard."
    },
    "DashboardElasticsearchDsl": {
      "type": "object",
      "properties": {
        "query": {
          "type": "object",
          "additionalProperties": true
        },
        "aggs": {
          "type": "object",
          "additionalProperties": true
        },
        "size": {
          "type": "number"
        },
        "from": {
          "type": "number"
        },
        "sort": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "additionalProperties": false,
      "description": "Elasticsearch DSL supported by dashboard data sources. Queries execute through Vertesia Store, so project/security filtering remains server-side."
    },
    "DashboardElasticsearchResultMapping": {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "const": "hits"
            }
          },
          "required": [
            "type"
          ],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "const": "aggregation_buckets"
            },
            "path": {
              "type": "string",
              "description": "Dot path under `aggregations` that contains a `buckets` array."
            },
            "keyField": {
              "type": "string",
              "description": "Output field name for bucket key. Defaults to `key`."
            },
            "countField": {
              "type": "string",
              "description": "Output field name for doc count. Defaults to `doc_count`."
            }
          },
          "required": [
            "type",
            "path"
          ],
          "additionalProperties": false
        }
      ],
      "type": "object",
      "required": [
        "type"
      ],
      "description": "How an Elasticsearch DSL result should be converted into Vega rows."
    },
    "DashboardLayout": {
      "type": "object",
      "properties": {
        "columns": {
          "type": "number",
          "description": "Number of columns in the grid (default: 2)"
        },
        "cellWidth": {
          "type": "number",
          "description": "Width of each cell in pixels (default: 600)"
        },
        "cellHeight": {
          "type": "number",
          "description": "Height of each cell in pixels (default: 400)"
        },
        "padding": {
          "type": "number",
          "description": "Padding between cells in pixels (default: 20)"
        }
      },
      "required": [
        "columns",
        "cellWidth",
        "cellHeight",
        "padding"
      ],
      "additionalProperties": false,
      "description": "Dashboard layout configuration."
    },
    "DashboardPanel": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "description": "Panel ID (auto-generated if not provided)"
        },
        "title": {
          "type": "string",
          "description": "Panel title"
        },
        "spec": {
          "type": "object",
          "additionalProperties": true,
          "description": "Vega or Vega-Lite specification"
        },
        "vegaLite": {
          "type": "boolean",
          "description": "Whether spec is Vega-Lite (default: true)"
        },
        "dataSources": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Query names that populate this panel's data sources"
        },
        "position": {
          "$ref": "#/$defs/DashboardPanelPosition",
          "description": "Position in the dashboard grid"
        }
      },
      "required": [
        "title",
        "spec",
        "dataSources",
        "position"
      ],
      "additionalProperties": false,
      "description": "Dashboard panel with Vega/Vega-Lite visualization."
    },
    "DashboardPanelPosition": {
      "type": "object",
      "properties": {
        "row": {
          "type": "number",
          "description": "Row index (0-based)"
        },
        "col": {
          "type": "number",
          "description": "Column index (0-based)"
        },
        "width": {
          "type": "number",
          "description": "Width in grid cells (default: 1)"
        },
        "height": {
          "type": "number",
          "description": "Height in grid cells (default: 1)"
        }
      },
      "required": [
        "row",
        "col"
      ],
      "additionalProperties": false,
      "description": "Panel position within the dashboard grid."
    },
    "DashboardQuery": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Query name (used as data source reference in Vega specs)"
        },
        "sql": {
          "type": "string",
          "description": "SQL query (SELECT only). Can include {{param_name}} placeholders for dynamic values."
        },
        "description": {
          "type": "string",
          "description": "Human-readable description"
        },
        "limit": {
          "type": "number",
          "description": "Maximum rows to return"
        },
        "parameters": {
          "$ref": "#/$defs/StringValueMap",
          "description": "Default values for SQL parameters. Keys are parameter names (without braces)."
        }
      },
      "required": [
        "name",
        "sql"
      ],
      "additionalProperties": false,
      "description": "Named SQL query that maps to a Vega data source. Supports parameterized SQL with {{param_name}} placeholders."
    },
    "DashboardSqlDataSource": {
      "type": "object",
      "properties": {
        "kind": {
          "type": "string",
          "const": "data_sql"
        },
        "query": {
          "type": "string",
          "description": "SQL query that returns all rows for the dashboard."
        },
        "queryLimit": {
          "type": "number",
          "description": "Maximum rows to return from the query."
        },
        "queryParameters": {
          "$ref": "#/$defs/StringValueMap",
          "description": "Default values for SQL {{param}} placeholders."
        }
      },
      "required": [
        "kind",
        "query"
      ],
      "additionalProperties": false,
      "description": "Dashboard data source backed by a Data Platform SQL query."
    },
    "DashboardStatus": {
      "type": "string",
      "enum": [
        "active",
        "archived"
      ],
      "description": "Dashboard lifecycle status."
    },
    "DashboardStoreElasticsearchDataSource": {
      "type": "object",
      "properties": {
        "kind": {
          "type": "string",
          "const": "store_es_dsl"
        },
        "dsl": {
          "$ref": "#/$defs/DashboardElasticsearchDsl",
          "description": "Elasticsearch DSL query executed through the secured Store query API."
        },
        "result": {
          "$ref": "#/$defs/DashboardElasticsearchResultMapping",
          "description": "Result mapping. Defaults to `hits`."
        }
      },
      "required": [
        "kind",
        "dsl"
      ],
      "additionalProperties": false,
      "description": "Dashboard data source backed by Vertesia Store Elasticsearch DSL."
    },
    "StringValueMap": {
      "type": "object",
      "additionalProperties": {
        "type": "string"
      }
    }
  }
}