databricks-aibi-dashboards

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

AI/BI Dashboard Skill

AI/BI仪表板开发技能

Create Databricks AI/BI dashboards (formerly Lakeview dashboards). Follow these guidelines strictly.
创建Databricks AI/BI仪表板(原Lakeview仪表板)。请严格遵循以下指南。

CRITICAL: MANDATORY VALIDATION WORKFLOW

重要提示:强制验证工作流

You MUST follow this workflow exactly. Skipping validation causes broken dashboards.
┌─────────────────────────────────────────────────────────────────────┐
│  STEP 1: Get table schemas via get_table_details(catalog, schema)  │
├─────────────────────────────────────────────────────────────────────┤
│  STEP 2: Write SQL queries for each dataset                        │
├─────────────────────────────────────────────────────────────────────┤
│  STEP 3: TEST EVERY QUERY via execute_sql() ← DO NOT SKIP!         │
│          - If query fails, FIX IT before proceeding                │
│          - Verify column names match what widgets will reference   │
│          - Verify data types are correct (dates, numbers, strings) │
├─────────────────────────────────────────────────────────────────────┤
│  STEP 4: Build dashboard JSON using ONLY verified queries          │
├─────────────────────────────────────────────────────────────────────┤
│  STEP 5: Deploy via create_or_update_dashboard()                   │
└─────────────────────────────────────────────────────────────────────┘
WARNING: If you deploy without testing queries, widgets WILL show "Invalid widget definition" errors!
您必须严格遵循此工作流。跳过验证会导致仪表板故障。
┌─────────────────────────────────────────────────────────────────────┐
│  步骤1:通过get_table_details(catalog, schema)获取表结构          │
├─────────────────────────────────────────────────────────────────────┤
│  步骤2:为每个数据集编写SQL查询                                    │
├─────────────────────────────────────────────────────────────────────┤
│  步骤3:通过execute_sql()测试所有查询 ← 请勿跳过!                 │
│          - 如果查询失败,修复后再继续                              │
│          - 验证列名称与小部件引用的名称一致                        │
│          - 验证数据类型正确(日期、数字、字符串)                  │
├─────────────────────────────────────────────────────────────────────┤
│  步骤4:仅使用已验证的查询构建仪表板JSON                          │
├─────────────────────────────────────────────────────────────────────┤
│  步骤5:通过create_or_update_dashboard()部署                      │
└─────────────────────────────────────────────────────────────────────┘
警告:如果不测试查询就部署,小部件将显示“无效小部件定义”错误!

Available MCP Tools

可用的MCP工具

ToolDescription
get_table_details
STEP 1: Get table schemas for designing queries
execute_sql
STEP 3: Test SQL queries - MANDATORY before deployment!
get_best_warehouse
Get available warehouse ID
create_or_update_dashboard
STEP 5: Deploy dashboard JSON (only after validation!)
get_dashboard
Get dashboard details by ID
list_dashboards
List dashboards in workspace
trash_dashboard
Move dashboard to trash
publish_dashboard
Publish dashboard for viewers
unpublish_dashboard
Unpublish a dashboard

工具说明
get_table_details
步骤1:获取表结构以设计查询
execute_sql
步骤3:测试SQL查询 - 部署前必须执行!
get_best_warehouse
获取可用的仓库ID
create_or_update_dashboard
步骤5:部署仪表板JSON(仅在验证后执行!)
get_dashboard
通过ID获取仪表板详情
list_dashboards
列出工作区中的仪表板
trash_dashboard
将仪表板移至回收站
publish_dashboard
发布仪表板供查看者访问
unpublish_dashboard
取消发布仪表板

Implementation Guidelines

实施指南

1) DATASET ARCHITECTURE (STRICT)

1) 数据集架构(严格要求)

  • One dataset per domain (e.g., orders, customers, products)
  • Exactly ONE valid SQL query per dataset (no multiple queries separated by
    ;
    )
  • Always use fully-qualified table names:
    catalog.schema.table_name
  • SELECT must include all dimensions needed by widgets and all derived columns via
    AS
    aliases
  • Put ALL business logic (CASE/WHEN, COALESCE, ratios) into the dataset SELECT with explicit aliases
  • Contract rule: Every widget
    fieldName
    must exactly match a dataset column or alias
  • 每个领域对应一个数据集(例如:订单、客户、产品)
  • 每个数据集只能有一个有效的SQL查询(不允许用
    ;
    分隔多个查询)
  • 始终使用完全限定的表名
    catalog.schema.table_name
  • SELECT语句必须包含小部件所需的所有维度,以及通过
    AS
    别名定义的所有派生列
  • 将所有业务逻辑(CASE/WHEN、COALESCE、比率)放入数据集的SELECT语句中,并使用明确的别名
  • 规则约束:每个小部件的
    fieldName
    必须与数据集的列或别名完全匹配

2) WIDGET FIELD EXPRESSIONS

2) 小部件字段表达式

CRITICAL: Field Name Matching Rule The
name
in
query.fields
MUST exactly match the
fieldName
in
encodings
. If they don't match, the widget shows "no selected fields to visualize" error!
Correct pattern for aggregations:
json
// In query.fields:
{"name": "sum(spend)", "expression": "SUM(`spend`)"}

// In encodings (must match!):
{"fieldName": "sum(spend)", "displayName": "Total Spend"}
WRONG - names don't match:
json
// In query.fields:
{"name": "spend", "expression": "SUM(`spend`)"}  // name is "spend"

// In encodings:
{"fieldName": "sum(spend)", ...}  // ERROR: "sum(spend)" ≠ "spend"
Allowed expressions in widget queries (you CANNOT use CAST or other SQL in expressions):
For numbers:
json
{"name": "sum(revenue)", "expression": "SUM(`revenue`)"}
{"name": "avg(price)", "expression": "AVG(`price`)"}
{"name": "count(orders)", "expression": "COUNT(`order_id`)"}
{"name": "countdistinct(customers)", "expression": "COUNT(DISTINCT `customer_id`)"}
{"name": "min(date)", "expression": "MIN(`order_date`)"}
{"name": "max(date)", "expression": "MAX(`order_date`)"}
For dates (use daily for timeseries, weekly/monthly for grouped comparisons):
json
{"name": "daily(date)", "expression": "DATE_TRUNC(\"DAY\", `date`)"}
{"name": "weekly(date)", "expression": "DATE_TRUNC(\"WEEK\", `date`)"}
{"name": "monthly(date)", "expression": "DATE_TRUNC(\"MONTH\", `date`)"}
Simple field reference (for pre-aggregated data):
json
{"name": "category", "expression": "`category`"}
If you need conditional logic or multi-field formulas, compute a derived column in the dataset SQL first.
重要提示:字段名称匹配规则
query.fields
中的
name
必须与
encodings
中的
fieldName
完全匹配。 如果不匹配,小部件将显示“没有选定的可视化字段”错误!
聚合的正确写法:
json
// 在query.fields中:
{"name": "sum(spend)", "expression": "SUM(`spend`)"}

// 在encodings中(必须匹配!):
{"fieldName": "sum(spend)", "displayName": "总支出"}
错误示例 - 名称不匹配:
json
// 在query.fields中:
{"name": "spend", "expression": "SUM(`spend`)"}  // 名称是"spend"

// 在encodings中:
{"fieldName": "sum(spend)", ...}  // 错误:"sum(spend)" ≠ "spend"
小部件查询中允许使用的表达式(不能在表达式中使用CAST或其他SQL语句):
针对数字:
json
{"name": "sum(revenue)", "expression": "SUM(`revenue`)"}
{"name": "avg(price)", "expression": "AVG(`price`)"}
{"name": "count(orders)", "expression": "COUNT(`order_id`)"}
{"name": "countdistinct(customers)", "expression": "COUNT(DISTINCT `customer_id`)"}
{"name": "min(date)", "expression": "MIN(`order_date`)"}
{"name": "max(date)", "expression": "MAX(`order_date`)"}
针对日期(时间序列使用日粒度,分组对比使用周/月粒度):
json
{"name": "daily(date)", "expression": "DATE_TRUNC(\"DAY\", `date`)"}
{"name": "weekly(date)", "expression": "DATE_TRUNC(\"WEEK\", `date`)"}
{"name": "monthly(date)", "expression": "DATE_TRUNC(\"MONTH\", `date`)"}
简单字段引用(针对预聚合数据):
json
{"name": "category", "expression": "`category`"}
如果需要条件逻辑或多字段公式,请先在数据集的SQL中计算派生列。

3) SPARK SQL PATTERNS

3) Spark SQL 写法

  • Date math:
    date_sub(current_date(), N)
    for days,
    add_months(current_date(), -N)
    for months
  • Date truncation:
    DATE_TRUNC('DAY'|'WEEK'|'MONTH'|'QUARTER'|'YEAR', column)
  • AVOID
    INTERVAL
    syntax - use functions instead
  • 日期计算:使用
    date_sub(current_date(), N)
    计算天数,使用
    add_months(current_date(), -N)
    计算月数
  • 日期截断:
    DATE_TRUNC('DAY'|'WEEK'|'MONTH'|'QUARTER'|'YEAR', column)
  • 避免使用
    INTERVAL
    语法 - 改用函数

4) LAYOUT (6-Column Grid, NO GAPS)

4) 布局(6列网格,无间隙)

Each widget has a position:
{"x": 0, "y": 0, "width": 2, "height": 4}
CRITICAL: Each row must fill width=6 exactly. No gaps allowed.
Recommended widget sizes:
Widget TypeWidthHeightNotes
Text header61Full width; use SEPARATE widgets for title and subtitle
Counter/KPI23-4NEVER height=2 - too cramped!
Line/Bar chart35-6Pair side-by-side to fill row
Pie chart35-6Needs space for legend
Full-width chart65-7For detailed time series
Table65-8Full width for readability
Standard dashboard structure:
text
y=0:  Title (w=6, h=1) - Dashboard title (use separate widget!)
y=1:  Subtitle (w=6, h=1) - Description (use separate widget!)
y=2:  KPIs (w=2 each, h=3) - 3 key metrics side-by-side
y=5:  Section header (w=6, h=1) - "Trends" or similar
y=6:  Charts (w=3 each, h=5) - Two charts side-by-side
y=11: Section header (w=6, h=1) - "Details"
y=12: Table (w=6, h=6) - Detailed data
每个小部件都有位置:
{"x": 0, "y": 0, "width": 2, "height": 4}
重要提示:每行的总宽度必须恰好为6。不允许有间隙。
推荐的小部件尺寸:
小部件类型宽度高度说明
文本标题61全屏宽度;标题和副标题使用单独的小部件
计数器/KPI23-4绝对不要用高度=2 - 太拥挤!
折线图/柱状图35-6并排摆放以填满整行
饼图35-6需要预留图例空间
全屏宽度图表65-7用于详细的时间序列
表格65-8全屏宽度以提升可读性
标准仪表板结构:
text
y=0: 标题(w=6, h=1)- 仪表板标题(使用单独的小部件!)
y=1: 副标题(w=6, h=1)- 描述(使用单独的小部件!)
y=2: KPIs(每个w=2, h=3)- 3个关键指标并排显示
y=5: 章节标题(w=6, h=1)- 如“趋势”等
y=6: 图表(每个w=3, h=5)- 两个图表并排显示
y=11: 章节标题(w=6, h=1)- 如“详情”
y=12: 表格(w=6, h=6)- 详细数据

5) CARDINALITY & READABILITY (CRITICAL)

5) 基数与可读性(重要提示)

Dashboard readability depends on limiting distinct values:
Dimension TypeMax ValuesExamples
Chart color/groups3-84 regions, 5 product lines, 3 tiers
Filters4-108 countries, 5 channels
High cardinalityTable onlycustomer_id, order_id, SKU
Before creating any chart with color/grouping:
  1. Check column cardinality (use
    get_table_details
    to see distinct values)
  2. If >10 distinct values, aggregate to higher level OR use TOP-N + "Other" bucket
  3. For high-cardinality dimensions, use a table widget instead of a chart
仪表板的可读性取决于限制不同值的数量:
维度类型最大值示例
图表颜色/分组3-84个地区、5个产品线、3个层级
过滤器4-108个国家、5个渠道
高基数仅用于表格customer_id、order_id、SKU
创建任何带颜色/分组的图表之前:
  1. 检查列的基数(使用
    get_table_details
    查看不同值的数量)
  2. 如果不同值超过10个,聚合到更高层级或使用TOP-N + “其他”分组
  3. 对于高基数维度,使用表格小部件而非图表

6) WIDGET SPECIFICATIONS

6) 小部件规范

Widget Naming Convention (CRITICAL):
  • widget.name
    : alphanumeric + hyphens + underscores ONLY (no spaces, parentheses, colons)
  • frame.title
    : human-readable name (any characters allowed)
  • widget.queries[0].name
    : always use
    "main_query"
CRITICAL VERSION REQUIREMENTS:
Widget TypeVersion
counter2
table2
filter-multi-select2
filter-single-select2
filter-date-range-picker2
bar3
line3
pie3
textN/A (no spec block)

Text (Headers/Descriptions):
  • CRITICAL: Text widgets do NOT use a spec block!
  • Use
    multilineTextboxSpec
    directly on the widget
  • Supports markdown:
    #
    ,
    ##
    ,
    ###
    ,
    **bold**
    ,
    *italic*
  • CRITICAL: Multiple items in the
    lines
    array are concatenated on a single line, NOT displayed as separate lines!
  • For title + subtitle, use separate text widgets at different y positions
json
// CORRECT: Separate widgets for title and subtitle
{
  "widget": {
    "name": "title",
    "multilineTextboxSpec": {
      "lines": ["## Dashboard Title"]
    }
  },
  "position": {"x": 0, "y": 0, "width": 6, "height": 1}
},
{
  "widget": {
    "name": "subtitle",
    "multilineTextboxSpec": {
      "lines": ["Description text here"]
    }
  },
  "position": {"x": 0, "y": 1, "width": 6, "height": 1}
}

// WRONG: Multiple lines concatenate into one line!
{
  "widget": {
    "name": "title-widget",
    "multilineTextboxSpec": {
      "lines": ["## Dashboard Title", "Description text here"]  // Becomes "## Dashboard TitleDescription text here"
    }
  },
  "position": {"x": 0, "y": 0, "width": 6, "height": 2}
}

Counter (KPI):
  • version
    : 2 (NOT 3!)
  • widgetType
    : "counter"
  • Percent values must be 0-1 in the data (not 0-100)
Two patterns for counters:
Pattern 1: Pre-aggregated dataset (1 row, no filters)
  • Dataset returns exactly 1 row
  • Use
    "disaggregated": true
    and simple field reference
  • Field
    name
    matches dataset column directly
json
{
  "widget": {
    "name": "total-revenue",
    "queries": [{
      "name": "main_query",
      "query": {
        "datasetName": "summary_ds",
        "fields": [{"name": "revenue", "expression": "`revenue`"}],
        "disaggregated": true
      }
    }],
    "spec": {
      "version": 2,
      "widgetType": "counter",
      "encodings": {
        "value": {"fieldName": "revenue", "displayName": "Total Revenue"}
      },
      "frame": {"showTitle": true, "title": "Total Revenue"}
    }
  },
  "position": {"x": 0, "y": 0, "width": 2, "height": 3}
}
Pattern 2: Aggregating widget (multi-row dataset, supports filters)
  • Dataset returns multiple rows (e.g., grouped by a filter dimension)
  • Use
    "disaggregated": false
    and aggregation expression
  • CRITICAL: Field
    name
    MUST match
    fieldName
    exactly (e.g.,
    "sum(spend)"
    )
json
{
  "widget": {
    "name": "total-spend",
    "queries": [{
      "name": "main_query",
      "query": {
        "datasetName": "by_category",
        "fields": [{"name": "sum(spend)", "expression": "SUM(`spend`)"}],
        "disaggregated": false
      }
    }],
    "spec": {
      "version": 2,
      "widgetType": "counter",
      "encodings": {
        "value": {"fieldName": "sum(spend)", "displayName": "Total Spend"}
      },
      "frame": {"showTitle": true, "title": "Total Spend"}
    }
  },
  "position": {"x": 0, "y": 0, "width": 2, "height": 3}
}

Table:
  • version
    : 2 (NOT 1 or 3!)
  • widgetType
    : "table"
  • Columns only need
    fieldName
    and
    displayName
    - no other properties!
  • Use
    "disaggregated": true
    for raw rows
json
{
  "widget": {
    "name": "details-table",
    "queries": [{
      "name": "main_query",
      "query": {
        "datasetName": "details_ds",
        "fields": [
          {"name": "name", "expression": "`name`"},
          {"name": "value", "expression": "`value`"}
        ],
        "disaggregated": true
      }
    }],
    "spec": {
      "version": 2,
      "widgetType": "table",
      "encodings": {
        "columns": [
          {"fieldName": "name", "displayName": "Name"},
          {"fieldName": "value", "displayName": "Value"}
        ]
      },
      "frame": {"showTitle": true, "title": "Details"}
    }
  },
  "position": {"x": 0, "y": 0, "width": 6, "height": 6}
}

Line / Bar Charts:
  • version
    : 3
  • widgetType
    : "line" or "bar"
  • Use
    x
    ,
    y
    , optional
    color
    encodings
  • scale.type
    :
    "temporal"
    (dates),
    "quantitative"
    (numbers),
    "categorical"
    (strings)
  • Use
    "disaggregated": true
    with pre-aggregated dataset data
Multiple Lines - Two Approaches:
  1. Multi-Y Fields (different metrics on same chart):
json
"y": {
  "scale": {"type": "quantitative"},
  "fields": [
    {"fieldName": "sum(orders)", "displayName": "Orders"},
    {"fieldName": "sum(returns)", "displayName": "Returns"}
  ]
}
  1. Color Grouping (same metric split by dimension):
json
"y": {"fieldName": "sum(revenue)", "scale": {"type": "quantitative"}},
"color": {"fieldName": "region", "scale": {"type": "categorical"}, "displayName": "Region"}
Bar Chart Modes:
  • Stacked (default): No
    mark
    field - bars stack on top of each other
  • Grouped: Add
    "mark": {"layout": "group"}
    - bars side-by-side for comparison
Pie Chart:
  • version
    : 3
  • widgetType
    : "pie"
  • angle
    : quantitative aggregate
  • color
    : categorical dimension
  • Limit to 3-8 categories for readability
小部件命名规范(重要提示):
  • widget.name
    :只能使用字母数字、连字符和下划线(不能有空格、括号、冒号)
  • frame.title
    :人类可读的名称(允许使用任何字符)
  • widget.queries[0].name
    :始终使用
    "main_query"
重要版本要求:
小部件类型版本
counter2
table2
filter-multi-select2
filter-single-select2
filter-date-range-picker2
bar3
line3
pie3
text无(不需要spec块)

文本(标题/描述):
  • 重要提示:文本小部件不需要spec块!
  • 直接在小部件上使用
    multilineTextboxSpec
  • 支持Markdown:
    #
    ##
    ###
    **粗体**
    *斜体*
  • 重要提示:
    lines
    数组中的多个项会被拼接成一行,而不是显示为单独的行!
  • 标题和副标题请使用单独的文本小部件,放在不同的y位置
json
// 正确示例:标题和副标题使用单独的小部件
{
  "widget": {
    "name": "title",
    "multilineTextboxSpec": {
      "lines": ["## 仪表板标题"]
    }
  },
  "position": {"x": 0, "y": 0, "width": 6, "height": 1}
},
{
  "widget": {
    "name": "subtitle",
    "multilineTextboxSpec": {
      "lines": ["描述文本"]
    }
  },
  "position": {"x": 0, "y": 1, "width": 6, "height": 1}
}

// 错误示例:多行文本会被拼接成一行!
{
  "widget": {
    "name": "title-widget",
    "multilineTextboxSpec": {
      "lines": ["## 仪表板标题", "描述文本"]  // 会变成"## 仪表板标题描述文本"
    }
  },
  "position": {"x": 0, "y": 0, "width": 6, "height": 2}
}

计数器(KPI):
  • version
    2(不是3!)
  • widgetType
    :"counter"
  • 百分比值在数据中必须是0-1(不是0-100)
计数器的两种写法:
写法1:预聚合数据集(1行,无过滤器)
  • 数据集返回恰好1行
  • 使用
    "disaggregated": true
    和简单字段引用
  • 字段
    name
    直接匹配数据集的列
json
{
  "widget": {
    "name": "total-revenue",
    "queries": [{
      "name": "main_query",
      "query": {
        "datasetName": "summary_ds",
        "fields": [{"name": "revenue", "expression": "`revenue`"}],
        "disaggregated": true
      }
    }],
    "spec": {
      "version": 2,
      "widgetType": "counter",
      "encodings": {
        "value": {"fieldName": "revenue", "displayName": "总收入"}
      },
      "frame": {"showTitle": true, "title": "总收入"}
    }
  },
  "position": {"x": 0, "y": 0, "width": 2, "height": 3}
}
写法2:聚合小部件(多行数据集,支持过滤器)
  • 数据集返回多行(例如:按过滤器维度分组)
  • 使用
    "disaggregated": false
    和聚合表达式
  • 重要提示:字段
    name
    必须与
    fieldName
    完全匹配(例如:
    "sum(spend)"
json
{
  "widget": {
    "name": "total-spend",
    "queries": [{
      "name": "main_query",
      "query": {
        "datasetName": "by_category",
        "fields": [{"name": "sum(spend)", "expression": "SUM(`spend`)"}],
        "disaggregated": false
      }
    }],
    "spec": {
      "version": 2,
      "widgetType": "counter",
      "encodings": {
        "value": {"fieldName": "sum(spend)", "displayName": "总支出"}
      },
      "frame": {"showTitle": true, "title": "总支出"}
    }
  },
  "position": {"x": 0, "y": 0, "width": 2, "height": 3}
}

表格:
  • version
    2(不是1或3!)
  • widgetType
    :"table"
  • 列只需要
    fieldName
    displayName
    - 不需要其他属性!
  • 对于原始行数据,使用
    "disaggregated": true
json
{
  "widget": {
    "name": "details-table",
    "queries": [{
      "name": "main_query",
      "query": {
        "datasetName": "details_ds",
        "fields": [
          {"name": "name", "expression": "`name`"},
          {"name": "value", "expression": "`value`"}
        ],
        "disaggregated": true
      }
    }],
    "spec": {
      "version": 2,
      "widgetType": "table",
      "encodings": {
        "columns": [
          {"fieldName": "name", "displayName": "名称"},
          {"fieldName": "value", "displayName": "值"}
        ]
      },
      "frame": {"showTitle": true, "title": "详情"}
    }
  },
  "position": {"x": 0, "y": 0, "width": 6, "height": 6}
}

折线图 / 柱状图:
  • version
    3
  • widgetType
    :"line"或"bar"
  • 使用
    x
    y
    、可选的
    color
    编码
  • scale.type
    "temporal"
    (日期)、
    "quantitative"
    (数字)、
    "categorical"
    (字符串)
  • 对于预聚合数据集,使用
    "disaggregated": true
多折线图的两种实现方式:
  1. 多Y字段(同一图表显示不同指标):
json
"y": {
  "scale": {"type": "quantitative"},
  "fields": [
    {"fieldName": "sum(orders)", "displayName": "订单数"},
    {"fieldName": "sum(returns)", "displayName": "退货数"}
  ]
}
  1. 颜色分组(同一指标按维度拆分):
json
"y": {"fieldName": "sum(revenue)", "scale": {"type": "quantitative"}},
"color": {"fieldName": "region", "scale": {"type": "categorical"}, "displayName": "地区"}
柱状图模式:
  • 堆叠(默认):不设置
    mark
    字段 - 柱状图堆叠显示
  • 分组:添加
    "mark": {"layout": "group"}
    - 柱状图并排显示以便对比
饼图:
  • version
    3
  • widgetType
    :"pie"
  • angle
    :数值型聚合
  • color
    :分类维度
  • 为了可读性,限制为3-8个类别

7) FILTERS (Global vs Page-Level)

7) 过滤器(全局 vs 页面级)

CRITICAL: Filter widgets use DIFFERENT widget types than charts!
  • Valid types:
    filter-multi-select
    ,
    filter-single-select
    ,
    filter-date-range-picker
  • DO NOT use
    widgetType: "filter"
    - this does not exist and will cause errors
  • Filters use
    spec.version: 2
  • ALWAYS include
    frame
    with
    showTitle: true
    for filter widgets
Filter widget types:
  • filter-date-range-picker
    : for DATE/TIMESTAMP fields
  • filter-single-select
    : categorical with single selection
  • filter-multi-select
    : categorical with multiple selections

重要提示:过滤器小部件使用的类型与图表不同!
  • 有效类型:
    filter-multi-select
    filter-single-select
    filter-date-range-picker
  • 不要使用
    widgetType: "filter"
    - 该类型不存在,会导致错误
  • 过滤器使用
    spec.version: 2
  • 始终为过滤器小部件添加带
    showTitle: true
    frame
过滤器小部件类型:
  • filter-date-range-picker
    :用于DATE/TIMESTAMP字段
  • filter-single-select
    :单选分类过滤器
  • filter-multi-select
    :多选分类过滤器

Global Filters vs Page-Level Filters

全局过滤器 vs 页面级过滤器

TypePlacementScopeUse Case
Global FilterDedicated page with
"pageType": "PAGE_TYPE_GLOBAL_FILTERS"
Affects ALL pages that have datasets with the filter fieldCross-dashboard filtering (e.g., date range, campaign)
Page-Level FilterRegular page with
"pageType": "PAGE_TYPE_CANVAS"
Affects ONLY widgets on that same pagePage-specific filtering (e.g., platform filter on breakdown page only)
Key Insight: A filter only affects datasets that contain the filter field. To have a filter affect only specific pages:
  1. Include the filter dimension in datasets for pages that should be filtered
  2. Exclude the filter dimension from datasets for pages that should NOT be filtered

类型位置范围使用场景
全局过滤器专用页面,设置
"pageType": "PAGE_TYPE_GLOBAL_FILTERS"
影响所有包含该过滤器字段的数据集的页面跨仪表板过滤(例如:日期范围、活动)
页面级过滤器普通页面,设置
"pageType": "PAGE_TYPE_CANVAS"
仅影响同一页面上的小部件页面专属过滤(例如:仅在细分页面上使用平台过滤器)
关键要点:过滤器仅影响包含该过滤器字段的数据集。要让过滤器仅影响特定页面:
  1. 在需要被过滤的页面的数据集中包含过滤器维度
  2. 在不需要被过滤的页面的数据集中排除过滤器维度

Filter Widget Structure

过滤器小部件结构

CRITICAL: Do NOT use
associative_filter_predicate_group
- it causes SQL errors! Use a simple field expression instead.
json
{
  "widget": {
    "name": "filter_region",
    "queries": [{
      "name": "ds_data_region",
      "query": {
        "datasetName": "ds_data",
        "fields": [
          {"name": "region", "expression": "`region`"}
        ],
        "disaggregated": false
      }
    }],
    "spec": {
      "version": 2,
      "widgetType": "filter-multi-select",
      "encodings": {
        "fields": [{
          "fieldName": "region",
          "displayName": "Region",
          "queryName": "ds_data_region"
        }]
      },
      "frame": {"showTitle": true, "title": "Region"}
    }
  },
  "position": {"x": 0, "y": 0, "width": 2, "height": 2}
}

重要提示:不要使用
associative_filter_predicate_group
- 会导致SQL错误!
改用简单的字段表达式。
json
{
  "widget": {
    "name": "filter_region",
    "queries": [{
      "name": "ds_data_region",
      "query": {
        "datasetName": "ds_data",
        "fields": [
          {"name": "region", "expression": "`region`"}
        ],
        "disaggregated": false
      }
    }],
    "spec": {
      "version": 2,
      "widgetType": "filter-multi-select",
      "encodings": {
        "fields": [{
          "fieldName": "region",
          "displayName": "地区",
          "queryName": "ds_data_region"
        }]
      },
      "frame": {"showTitle": true, "title": "地区"}
    }
  },
  "position": {"x": 0, "y": 0, "width": 2, "height": 2}
}

Global Filter Example

全局过滤器示例

Place on a dedicated filter page:
json
{
  "name": "filters",
  "displayName": "Filters",
  "pageType": "PAGE_TYPE_GLOBAL_FILTERS",
  "layout": [
    {
      "widget": {
        "name": "filter_campaign",
        "queries": [{
          "name": "ds_campaign",
          "query": {
            "datasetName": "overview",
            "fields": [{"name": "campaign_name", "expression": "`campaign_name`"}],
            "disaggregated": false
          }
        }],
        "spec": {
          "version": 2,
          "widgetType": "filter-multi-select",
          "encodings": {
            "fields": [{
              "fieldName": "campaign_name",
              "displayName": "Campaign",
              "queryName": "ds_campaign"
            }]
          },
          "frame": {"showTitle": true, "title": "Campaign"}
        }
      },
      "position": {"x": 0, "y": 0, "width": 2, "height": 2}
    }
  ]
}

放置在专用的过滤器页面:
json
{
  "name": "filters",
  "displayName": "过滤器",
  "pageType": "PAGE_TYPE_GLOBAL_FILTERS",
  "layout": [
    {
      "widget": {
        "name": "filter_campaign",
        "queries": [{
          "name": "ds_campaign",
          "query": {
            "datasetName": "overview",
            "fields": [{"name": "campaign_name", "expression": "`campaign_name`"}],
            "disaggregated": false
          }
        }],
        "spec": {
          "version": 2,
          "widgetType": "filter-multi-select",
          "encodings": {
            "fields": [{
              "fieldName": "campaign_name",
              "displayName": "营销活动",
              "queryName": "ds_campaign"
            }]
          },
          "frame": {"showTitle": true, "title": "营销活动"}
        }
      },
      "position": {"x": 0, "y": 0, "width": 2, "height": 2}
    }
  ]
}

Page-Level Filter Example

页面级过滤器示例

Place directly on a canvas page (affects only that page):
json
{
  "name": "platform_breakdown",
  "displayName": "Platform Breakdown",
  "pageType": "PAGE_TYPE_CANVAS",
  "layout": [
    {
      "widget": {
        "name": "page-title",
        "multilineTextboxSpec": {"lines": ["## Platform Breakdown"]}
      },
      "position": {"x": 0, "y": 0, "width": 4, "height": 1}
    },
    {
      "widget": {
        "name": "filter_platform",
        "queries": [{
          "name": "ds_platform",
          "query": {
            "datasetName": "platform_data",
            "fields": [{"name": "platform", "expression": "`platform`"}],
            "disaggregated": false
          }
        }],
        "spec": {
          "version": 2,
          "widgetType": "filter-multi-select",
          "encodings": {
            "fields": [{
              "fieldName": "platform",
              "displayName": "Platform",
              "queryName": "ds_platform"
            }]
          },
          "frame": {"showTitle": true, "title": "Platform"}
        }
      },
      "position": {"x": 4, "y": 0, "width": 2, "height": 2}
    }
    // ... other widgets on this page
  ]
}

Filter Layout Guidelines:
  • Global filters: Position on dedicated filter page, stack vertically at
    x=0
  • Page-level filters: Position in header area of page (e.g., top-right corner)
  • Typical sizing:
    width: 2, height: 2
直接放置在画布页面(仅影响该页面):
json
{
  "name": "platform_breakdown",
  "displayName": "平台细分",
  "pageType": "PAGE_TYPE_CANVAS",
  "layout": [
    {
      "widget": {
        "name": "page-title",
        "multilineTextboxSpec": {"lines": ["## 平台细分"]}
      },
      "position": {"x": 0, "y": 0, "width": 4, "height": 1}
    },
    {
      "widget": {
        "name": "filter_platform",
        "queries": [{
          "name": "ds_platform",
          "query": {
            "datasetName": "platform_data",
            "fields": [{"name": "platform", "expression": "`platform`"}],
            "disaggregated": false
          }
        }],
        "spec": {
          "version": 2,
          "widgetType": "filter-multi-select",
          "encodings": {
            "fields": [{
              "fieldName": "platform",
              "displayName": "平台",
              "queryName": "ds_platform"
            }]
          },
          "frame": {"showTitle": true, "title": "平台"}
        }
      },
      "position": {"x": 4, "y": 0, "width": 2, "height": 2}
    }
    // ... 该页面上的其他小部件
  ]
}

过滤器布局指南:
  • 全局过滤器:放置在专用过滤器页面,垂直堆叠在
    x=0
    位置
  • 页面级过滤器:放置在页面的标题区域(例如:右上角)
  • 典型尺寸:
    width: 2, height: 2

8) QUALITY CHECKLIST

8) 质量检查清单

Before deploying, verify:
  1. All widget names use only alphanumeric + hyphens + underscores
  2. All rows sum to width=6 with no gaps
  3. KPIs use height 3-4, charts use height 5-6
  4. Chart dimensions have ≤8 distinct values
  5. All widget fieldNames match dataset columns exactly
  6. Field
    name
    in query.fields matches
    fieldName
    in encodings exactly
    (e.g., both
    "sum(spend)"
    )
  7. Counter datasets: use
    disaggregated: true
    for 1-row datasets,
    disaggregated: false
    with aggregation for multi-row
  8. Percent values are 0-1 (not 0-100)
  9. SQL uses Spark syntax (date_sub, not INTERVAL)
  10. All SQL queries tested via
    execute_sql
    and return expected data

部署前,请验证:
  1. 所有小部件名称仅使用字母数字、连字符和下划线
  2. 每行的总宽度为6,无间隙
  3. KPI使用高度3-4,图表使用高度5-6
  4. 图表维度的不同值≤8个
  5. 所有小部件的fieldName与数据集列完全匹配
  6. query.fields中的字段
    name
    与encodings中的
    fieldName
    完全匹配
    (例如:均为
    "sum(spend)"
  7. 计数器数据集:1行数据集使用
    disaggregated: true
    ,多行数据集使用
    disaggregated: false
    并配合聚合
  8. 百分比值为0-1(不是0-100)
  9. SQL使用Spark语法(date_sub,而非INTERVAL)
  10. 所有SQL查询已通过
    execute_sql
    测试并返回预期数据

Complete Example

完整示例

python
import json
python
import json

Step 1: Check table schema

步骤1:检查表结构

table_info = get_table_details(catalog="samples", schema="nyctaxi")
table_info = get_table_details(catalog="samples", schema="nyctaxi")

Step 2: Test queries

步骤2:测试查询

execute_sql("SELECT COUNT() as trips, AVG(fare_amount) as avg_fare, AVG(trip_distance) as avg_distance FROM samples.nyctaxi.trips") execute_sql(""" SELECT pickup_zip, COUNT() as trip_count FROM samples.nyctaxi.trips GROUP BY pickup_zip ORDER BY trip_count DESC LIMIT 10 """)
execute_sql("SELECT COUNT() as trips, AVG(fare_amount) as avg_fare, AVG(trip_distance) as avg_distance FROM samples.nyctaxi.trips") execute_sql(""" SELECT pickup_zip, COUNT() as trip_count FROM samples.nyctaxi.trips GROUP BY pickup_zip ORDER BY trip_count DESC LIMIT 10 """)

Step 3: Build dashboard JSON

步骤3:构建仪表板JSON

dashboard = { "datasets": [ { "name": "summary", "displayName": "Summary Stats", "queryLines": [ "SELECT COUNT() as trips, AVG(fare_amount) as avg_fare, ", "AVG(trip_distance) as avg_distance ", "FROM samples.nyctaxi.trips " ] }, { "name": "by_zip", "displayName": "Trips by ZIP", "queryLines": [ "SELECT pickup_zip, COUNT() as trip_count ", "FROM samples.nyctaxi.trips ", "GROUP BY pickup_zip ", "ORDER BY trip_count DESC ", "LIMIT 10 " ] } ], "pages": [{ "name": "overview", "displayName": "NYC Taxi Overview", "pageType": "PAGE_TYPE_CANVAS", "layout": [ # Text header - NO spec block! Use SEPARATE widgets for title and subtitle! { "widget": { "name": "title", "multilineTextboxSpec": { "lines": ["## NYC Taxi Dashboard"] } }, "position": {"x": 0, "y": 0, "width": 6, "height": 1} }, { "widget": { "name": "subtitle", "multilineTextboxSpec": { "lines": ["Trip statistics and analysis"] } }, "position": {"x": 0, "y": 1, "width": 6, "height": 1} }, # Counter - version 2, width 2! { "widget": { "name": "total-trips", "queries": [{ "name": "main_query", "query": { "datasetName": "summary", "fields": [{"name": "trips", "expression": "
trips
"}], "disaggregated": True } }], "spec": { "version": 2, "widgetType": "counter", "encodings": { "value": {"fieldName": "trips", "displayName": "Total Trips"} }, "frame": {"title": "Total Trips", "showTitle": True} } }, "position": {"x": 0, "y": 2, "width": 2, "height": 3} }, { "widget": { "name": "avg-fare", "queries": [{ "name": "main_query", "query": { "datasetName": "summary", "fields": [{"name": "avg_fare", "expression": "
avg_fare
"}], "disaggregated": True } }], "spec": { "version": 2, "widgetType": "counter", "encodings": { "value": {"fieldName": "avg_fare", "displayName": "Avg Fare"} }, "frame": {"title": "Average Fare", "showTitle": True} } }, "position": {"x": 2, "y": 2, "width": 2, "height": 3} }, { "widget": { "name": "total-distance", "queries": [{ "name": "main_query", "query": { "datasetName": "summary", "fields": [{"name": "avg_distance", "expression": "
avg_distance
"}], "disaggregated": True } }], "spec": { "version": 2, "widgetType": "counter", "encodings": { "value": {"fieldName": "avg_distance", "displayName": "Avg Distance"} }, "frame": {"title": "Average Distance", "showTitle": True} } }, "position": {"x": 4, "y": 2, "width": 2, "height": 3} }, # Bar chart - version 3 { "widget": { "name": "trips-by-zip", "queries": [{ "name": "main_query", "query": { "datasetName": "by_zip", "fields": [ {"name": "pickup_zip", "expression": "
pickup_zip
"}, {"name": "trip_count", "expression": "
trip_count
"} ], "disaggregated": True } }], "spec": { "version": 3, "widgetType": "bar", "encodings": { "x": {"fieldName": "pickup_zip", "scale": {"type": "categorical"}, "displayName": "ZIP"}, "y": {"fieldName": "trip_count", "scale": {"type": "quantitative"}, "displayName": "Trips"} }, "frame": {"title": "Trips by Pickup ZIP", "showTitle": True} } }, "position": {"x": 0, "y": 5, "width": 6, "height": 5} }, # Table - version 2, minimal column props! { "widget": { "name": "zip-table", "queries": [{ "name": "main_query", "query": { "datasetName": "by_zip", "fields": [ {"name": "pickup_zip", "expression": "
pickup_zip
"}, {"name": "trip_count", "expression": "
trip_count
"} ], "disaggregated": True } }], "spec": { "version": 2, "widgetType": "table", "encodings": { "columns": [ {"fieldName": "pickup_zip", "displayName": "ZIP Code"}, {"fieldName": "trip_count", "displayName": "Trip Count"} ] }, "frame": {"title": "Top ZIP Codes", "showTitle": True} } }, "position": {"x": 0, "y": 10, "width": 6, "height": 5} } ] }] }
dashboard = { "datasets": [ { "name": "summary", "displayName": "汇总统计", "queryLines": [ "SELECT COUNT() as trips, AVG(fare_amount) as avg_fare, ", "AVG(trip_distance) as avg_distance ", "FROM samples.nyctaxi.trips " ] }, { "name": "by_zip", "displayName": "按ZIP码统计订单", "queryLines": [ "SELECT pickup_zip, COUNT() as trip_count ", "FROM samples.nyctaxi.trips ", "GROUP BY pickup_zip ", "ORDER BY trip_count DESC ", "LIMIT 10 " ] } ], "pages": [{ "name": "overview", "displayName": "纽约出租车概览", "pageType": "PAGE_TYPE_CANVAS", "layout": [ # 文本标题 - 不需要spec块!标题和副标题使用单独的小部件! { "widget": { "name": "title", "multilineTextboxSpec": { "lines": ["## 纽约出租车仪表板"] } }, "position": {"x": 0, "y": 0, "width": 6, "height": 1} }, { "widget": { "name": "subtitle", "multilineTextboxSpec": { "lines": ["订单统计与分析"] } }, "position": {"x": 0, "y": 1, "width": 6, "height": 1} }, # 计数器 - 版本2,宽度2! { "widget": { "name": "total-trips", "queries": [{ "name": "main_query", "query": { "datasetName": "summary", "fields": [{"name": "trips", "expression": "
trips
"}], "disaggregated": True } }], "spec": { "version": 2, "widgetType": "counter", "encodings": { "value": {"fieldName": "trips", "displayName": "总订单数"} }, "frame": {"title": "总订单数", "showTitle": True} } }, "position": {"x": 0, "y": 2, "width": 2, "height": 3} }, { "widget": { "name": "avg-fare", "queries": [{ "name": "main_query", "query": { "datasetName": "summary", "fields": [{"name": "avg_fare", "expression": "
avg_fare
"}], "disaggregated": True } }], "spec": { "version": 2, "widgetType": "counter", "encodings": { "value": {"fieldName": "avg_fare", "displayName": "平均费用"} }, "frame": {"title": "平均费用", "showTitle": True} } }, "position": {"x": 2, "y": 2, "width": 2, "height": 3} }, { "widget": { "name": "total-distance", "queries": [{ "name": "main_query", "query": { "datasetName": "summary", "fields": [{"name": "avg_distance", "expression": "
avg_distance
"}], "disaggregated": True } }], "spec": { "version": 2, "widgetType": "counter", "encodings": { "value": {"fieldName": "avg_distance", "displayName": "平均距离"} }, "frame": {"title": "平均距离", "showTitle": True} } }, "position": {"x": 4, "y": 2, "width": 2, "height": 3} }, # 柱状图 - 版本3 { "widget": { "name": "trips-by-zip", "queries": [{ "name": "main_query", "query": { "datasetName": "by_zip", "fields": [ {"name": "pickup_zip", "expression": "
pickup_zip
"}, {"name": "trip_count", "expression": "
trip_count
"} ], "disaggregated": True } }], "spec": { "version": 3, "widgetType": "bar", "encodings": { "x": {"fieldName": "pickup_zip", "scale": {"type": "categorical"}, "displayName": "ZIP码"}, "y": {"fieldName": "trip_count", "scale": {"type": "quantitative"}, "displayName": "订单数"} }, "frame": {"title": "按上车ZIP码统计订单", "showTitle": True} } }, "position": {"x": 0, "y": 5, "width": 6, "height": 5} }, # 表格 - 版本2,仅保留必要的列属性! { "widget": { "name": "zip-table", "queries": [{ "name": "main_query", "query": { "datasetName": "by_zip", "fields": [ {"name": "pickup_zip", "expression": "
pickup_zip
"}, {"name": "trip_count", "expression": "
trip_count
"} ], "disaggregated": True } }], "spec": { "version": 2, "widgetType": "table", "encodings": { "columns": [ {"fieldName": "pickup_zip", "displayName": "ZIP码"}, {"fieldName": "trip_count", "displayName": "订单数"} ] }, "frame": {"title": "热门ZIP码", "showTitle": True} } }, "position": {"x": 0, "y": 10, "width": 6, "height": 5} } ] }] }

Step 4: Deploy

步骤4:部署

result = create_or_update_dashboard( display_name="NYC Taxi Dashboard", parent_path="/Workspace/Users/me/dashboards", serialized_dashboard=json.dumps(dashboard), warehouse_id=get_best_warehouse(), ) print(result["url"])
undefined
result = create_or_update_dashboard( display_name="纽约出租车仪表板", parent_path="/Workspace/Users/me/dashboards", serialized_dashboard=json.dumps(dashboard), warehouse_id=get_best_warehouse(), ) print(result["url"])
undefined

Complete Example with Filters

带过滤器的完整示例

python
import json
python
import json

Dashboard with a global filter for region

带地区全局过滤器的仪表板

dashboard_with_filters = { "datasets": [ { "name": "sales", "displayName": "Sales Data", "queryLines": [ "SELECT region, SUM(revenue) as total_revenue ", "FROM catalog.schema.sales ", "GROUP BY region" ] } ], "pages": [ { "name": "overview", "displayName": "Sales Overview", "pageType": "PAGE_TYPE_CANVAS", "layout": [ { "widget": { "name": "total-revenue", "queries": [{ "name": "main_query", "query": { "datasetName": "sales", "fields": [{"name": "total_revenue", "expression": "
total_revenue
"}], "disaggregated": True } }], "spec": { "version": 2, # Version 2 for counters! "widgetType": "counter", "encodings": { "value": {"fieldName": "total_revenue", "displayName": "Total Revenue"} }, "frame": {"title": "Total Revenue", "showTitle": True} } }, "position": {"x": 0, "y": 0, "width": 6, "height": 3} } ] }, { "name": "filters", "displayName": "Filters", "pageType": "PAGE_TYPE_GLOBAL_FILTERS", # Required for global filter page! "layout": [ { "widget": { "name": "filter_region", "queries": [{ "name": "ds_sales_region", "query": { "datasetName": "sales", "fields": [ {"name": "region", "expression": "
region
"} # DO NOT use associative_filter_predicate_group - causes SQL errors! ], "disaggregated": False # False for filters! } }], "spec": { "version": 2, # Version 2 for filters! "widgetType": "filter-multi-select", # NOT "filter"! "encodings": { "fields": [{ "fieldName": "region", "displayName": "Region", "queryName": "ds_sales_region" # Must match query name! }] }, "frame": {"showTitle": True, "title": "Region"} # Always show title! } }, "position": {"x": 0, "y": 0, "width": 2, "height": 2} } ] } ] }
dashboard_with_filters = { "datasets": [ { "name": "sales", "displayName": "销售数据", "queryLines": [ "SELECT region, SUM(revenue) as total_revenue ", "FROM catalog.schema.sales ", "GROUP BY region" ] } ], "pages": [ { "name": "overview", "displayName": "销售概览", "pageType": "PAGE_TYPE_CANVAS", "layout": [ { "widget": { "name": "total-revenue", "queries": [{ "name": "main_query", "query": { "datasetName": "sales", "fields": [{"name": "total_revenue", "expression": "
total_revenue
"}], "disaggregated": True } }], "spec": { "version": 2, # 计数器使用版本2! "widgetType": "counter", "encodings": { "value": {"fieldName": "total_revenue", "displayName": "总收入"} }, "frame": {"title": "总收入", "showTitle": True} } }, "position": {"x": 0, "y": 0, "width": 6, "height": 3} } ] }, { "name": "filters", "displayName": "过滤器", "pageType": "PAGE_TYPE_GLOBAL_FILTERS", # 全局过滤器页面必须设置此属性! "layout": [ { "widget": { "name": "filter_region", "queries": [{ "name": "ds_sales_region", "query": { "datasetName": "sales", "fields": [ {"name": "region", "expression": "
region
"} # 不要使用associative_filter_predicate_group - 会导致SQL错误! ], "disaggregated": False # 过滤器使用False! } }], "spec": { "version": 2, # 过滤器使用版本2! "widgetType": "filter-multi-select", # 不要使用"filter"! "encodings": { "fields": [{ "fieldName": "region", "displayName": "地区", "queryName": "ds_sales_region" # 必须与查询名称匹配! }] }, "frame": {"showTitle": true, "title": "地区"} # 始终显示标题! } }, "position": {"x": 0, "y": 0, "width": 2, "height": 2} } ] } ] }

Deploy with filters

部署带过滤器的仪表板

result = create_or_update_dashboard( display_name="Sales Dashboard with Filters", parent_path="/Workspace/Users/me/dashboards", serialized_dashboard=json.dumps(dashboard_with_filters), warehouse_id=get_best_warehouse(), ) print(result["url"])
undefined
result = create_or_update_dashboard( display_name="带过滤器的销售仪表板", parent_path="/Workspace/Users/me/dashboards", serialized_dashboard=json.dumps(dashboard_with_filters), warehouse_id=get_best_warehouse(), ) print(result["url"])
undefined

Troubleshooting

故障排除

Widget shows "no selected fields to visualize"

小部件显示“没有选定的可视化字段”

This is a field name mismatch error. The
name
in
query.fields
must exactly match the
fieldName
in
encodings
.
Fix: Ensure names match exactly:
json
// WRONG - names don't match
"fields": [{"name": "spend", "expression": "SUM(`spend`)"}]
"encodings": {"value": {"fieldName": "sum(spend)", ...}}  // ERROR!

// CORRECT - names match
"fields": [{"name": "sum(spend)", "expression": "SUM(`spend`)"}]
"encodings": {"value": {"fieldName": "sum(spend)", ...}}  // OK!
这是字段名称不匹配错误。
query.fields
中的
name
必须与
encodings
中的
fieldName
完全匹配。
修复方法: 确保名称完全匹配:
json
// 错误示例 - 名称不匹配
"fields": [{"name": "spend", "expression": "SUM(`spend`)"}]
"encodings": {"value": {"fieldName": "sum(spend)", ...}}  // 错误!

// 正确示例 - 名称匹配
"fields": [{"name": "sum(spend)", "expression": "SUM(`spend`)"}]
"encodings": {"value": {"fieldName": "sum(spend)", ...}}  // 正确!

Widget shows "Invalid widget definition"

小部件显示“无效小部件定义”

Check version numbers:
  • Counters:
    version: 2
  • Tables:
    version: 2
  • Filters:
    version: 2
  • Bar/Line/Pie charts:
    version: 3
Text widget errors:
  • Text widgets must NOT have a
    spec
    block
  • Use
    multilineTextboxSpec
    directly on the widget object
  • Do NOT use
    widgetType: "text"
    - this is invalid
Table widget errors:
  • Use
    version: 2
    (NOT 1 or 3)
  • Column objects only need
    fieldName
    and
    displayName
  • Do NOT add
    type
    ,
    numberFormat
    , or other column properties
Counter widget errors:
  • Use
    version: 2
    (NOT 3)
  • Ensure dataset returns exactly 1 row
检查版本号:
  • 计数器:
    version: 2
  • 表格:
    version: 2
  • 过滤器:
    version: 2
  • 柱状图/折线图/饼图:
    version: 3
文本小部件错误:
  • 文本小部件不能有
    spec
  • 直接在小部件对象上使用
    multilineTextboxSpec
  • 不要使用
    widgetType: "text"
    - 该类型无效
表格小部件错误:
  • 使用
    version: 2
    (不是1或3)
  • 列对象只需要
    fieldName
    displayName
  • 不要添加
    type
    numberFormat
    或其他列属性
计数器小部件错误:
  • 使用
    version: 2
    (不是3)
  • 确保数据集返回恰好1行

Dashboard shows empty widgets

仪表板显示空小部件

  • Run the dataset SQL query directly to check data exists
  • Verify column aliases match widget field expressions
  • Check
    disaggregated
    flag (should be
    true
    for pre-aggregated data)
  • 直接运行数据集的SQL查询以确认数据存在
  • 验证列别名与小部件字段表达式匹配
  • 检查
    disaggregated
    标志(预聚合数据应设为
    true

Layout has gaps

布局有间隙

  • Ensure each row sums to width=6
  • Check that y positions don't skip values
  • 确保每行的总宽度为6
  • 检查y位置是否有跳跃

Filter shows "Invalid widget definition"

过滤器显示“无效小部件定义”

  • Check
    widgetType
    is one of:
    filter-multi-select
    ,
    filter-single-select
    ,
    filter-date-range-picker
  • DO NOT use
    widgetType: "filter"
    - this is invalid
  • Verify
    spec.version
    is
    2
  • Ensure
    queryName
    in encodings matches the query
    name
  • Confirm
    disaggregated: false
    in filter queries
  • Ensure
    frame
    with
    showTitle: true
    is included
  • 检查
    widgetType
    是否为以下之一:
    filter-multi-select
    filter-single-select
    filter-date-range-picker
  • 不要使用
    widgetType: "filter"
    - 该类型无效
  • 验证
    spec.version
    2
  • 确保encodings中的
    queryName
    与查询的
    name
    匹配
  • 确认过滤器查询中的
    disaggregated: false
  • 确保包含带
    showTitle: true
    frame

Filter not affecting expected pages

过滤器未影响预期页面

  • Global filters (on
    PAGE_TYPE_GLOBAL_FILTERS
    page) affect all datasets containing the filter field
  • Page-level filters (on
    PAGE_TYPE_CANVAS
    page) only affect widgets on that same page
  • A filter only works on datasets that include the filter dimension column
  • 全局过滤器(在
    PAGE_TYPE_GLOBAL_FILTERS
    页面)影响所有包含该过滤器字段的数据集
  • 页面级过滤器(在
    PAGE_TYPE_CANVAS
    页面)仅影响同一页面上的小部件
  • 过滤器仅对包含该过滤器维度列的数据集有效

Filter shows "UNRESOLVED_COLUMN" error for
associative_filter_predicate_group

过滤器显示
associative_filter_predicate_group
的“UNRESOLVED_COLUMN”错误

  • DO NOT use
    COUNT_IF(\
    associative_filter_predicate_group`)` in filter queries
  • This internal expression causes SQL errors when the dashboard executes queries
  • Use a simple field expression instead:
    {"name": "field", "expression": "\
    field`"}`
  • 不要在过滤器查询中使用
    COUNT_IF(\
    associative_filter_predicate_group`)`
  • 该内部表达式会导致仪表板执行查询时出现SQL错误
  • 改用简单的字段表达式:
    {"name": "field", "expression": "\
    field`"}`

Text widget shows title and description on same line

文本小部件的标题和描述显示在同一行

  • Multiple items in the
    lines
    array are concatenated, not displayed on separate lines
  • Use separate text widgets for title and subtitle at different y positions
  • Example: title at y=0 with height=1, subtitle at y=1 with height=1
  • lines
    数组中的多个项会被拼接,而不是显示为单独的行
  • 标题和副标题请使用单独的文本小部件,放在不同的y位置
  • 示例:标题在y=0,高度=1;副标题在y=1,高度=1