databricks-aibi-dashboards
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAI/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工具
| Tool | Description |
|---|---|
| STEP 1: Get table schemas for designing queries |
| STEP 3: Test SQL queries - MANDATORY before deployment! |
| Get available warehouse ID |
| STEP 5: Deploy dashboard JSON (only after validation!) |
| Get dashboard details by ID |
| List dashboards in workspace |
| Move dashboard to trash |
| Publish dashboard for viewers |
| Unpublish a dashboard |
| 工具 | 说明 |
|---|---|
| 步骤1:获取表结构以设计查询 |
| 步骤3:测试SQL查询 - 部署前必须执行! |
| 获取可用的仓库ID |
| 步骤5:部署仪表板JSON(仅在验证后执行!) |
| 通过ID获取仪表板详情 |
| 列出工作区中的仪表板 |
| 将仪表板移至回收站 |
| 发布仪表板供查看者访问 |
| 取消发布仪表板 |
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 aliases
AS - Put ALL business logic (CASE/WHEN, COALESCE, ratios) into the dataset SELECT with explicit aliases
- Contract rule: Every widget must exactly match a dataset column or alias
fieldName
- 每个领域对应一个数据集(例如:订单、客户、产品)
- 每个数据集只能有一个有效的SQL查询(不允许用分隔多个查询)
; - 始终使用完全限定的表名:
catalog.schema.table_name - SELECT语句必须包含小部件所需的所有维度,以及通过别名定义的所有派生列
AS - 将所有业务逻辑(CASE/WHEN、COALESCE、比率)放入数据集的SELECT语句中,并使用明确的别名
- 规则约束:每个小部件的必须与数据集的列或别名完全匹配
fieldName
2) WIDGET FIELD EXPRESSIONS
2) 小部件字段表达式
CRITICAL: Field Name Matching Rule TheinnameMUST exactly match thequery.fieldsinfieldName. If they don't match, the widget shows "no selected fields to visualize" error!encodings
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: for days,
date_sub(current_date(), N)for monthsadd_months(current_date(), -N) - Date truncation:
DATE_TRUNC('DAY'|'WEEK'|'MONTH'|'QUARTER'|'YEAR', column) - AVOID syntax - use functions instead
INTERVAL
- 日期计算:使用计算天数,使用
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 Type | Width | Height | Notes |
|---|---|---|---|
| Text header | 6 | 1 | Full width; use SEPARATE widgets for title and subtitle |
| Counter/KPI | 2 | 3-4 | NEVER height=2 - too cramped! |
| Line/Bar chart | 3 | 5-6 | Pair side-by-side to fill row |
| Pie chart | 3 | 5-6 | Needs space for legend |
| Full-width chart | 6 | 5-7 | For detailed time series |
| Table | 6 | 5-8 | Full 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。不允许有间隙。
推荐的小部件尺寸:
| 小部件类型 | 宽度 | 高度 | 说明 |
|---|---|---|---|
| 文本标题 | 6 | 1 | 全屏宽度;标题和副标题使用单独的小部件 |
| 计数器/KPI | 2 | 3-4 | 绝对不要用高度=2 - 太拥挤! |
| 折线图/柱状图 | 3 | 5-6 | 并排摆放以填满整行 |
| 饼图 | 3 | 5-6 | 需要预留图例空间 |
| 全屏宽度图表 | 6 | 5-7 | 用于详细的时间序列 |
| 表格 | 6 | 5-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 Type | Max Values | Examples |
|---|---|---|
| Chart color/groups | 3-8 | 4 regions, 5 product lines, 3 tiers |
| Filters | 4-10 | 8 countries, 5 channels |
| High cardinality | Table only | customer_id, order_id, SKU |
Before creating any chart with color/grouping:
- Check column cardinality (use to see distinct values)
get_table_details - If >10 distinct values, aggregate to higher level OR use TOP-N + "Other" bucket
- For high-cardinality dimensions, use a table widget instead of a chart
仪表板的可读性取决于限制不同值的数量:
| 维度类型 | 最大值 | 示例 |
|---|---|---|
| 图表颜色/分组 | 3-8 | 4个地区、5个产品线、3个层级 |
| 过滤器 | 4-10 | 8个国家、5个渠道 |
| 高基数 | 仅用于表格 | customer_id、order_id、SKU |
创建任何带颜色/分组的图表之前:
- 检查列的基数(使用查看不同值的数量)
get_table_details - 如果不同值超过10个,聚合到更高层级或使用TOP-N + “其他”分组
- 对于高基数维度,使用表格小部件而非图表
6) WIDGET SPECIFICATIONS
6) 小部件规范
Widget Naming Convention (CRITICAL):
- : alphanumeric + hyphens + underscores ONLY (no spaces, parentheses, colons)
widget.name - : human-readable name (any characters allowed)
frame.title - : always use
widget.queries[0].name"main_query"
CRITICAL VERSION REQUIREMENTS:
| Widget Type | Version |
|---|---|
| counter | 2 |
| table | 2 |
| filter-multi-select | 2 |
| filter-single-select | 2 |
| filter-date-range-picker | 2 |
| bar | 3 |
| line | 3 |
| pie | 3 |
| text | N/A (no spec block) |
Text (Headers/Descriptions):
- CRITICAL: Text widgets do NOT use a spec block!
- Use directly on the widget
multilineTextboxSpec - Supports markdown: ,
#,##,###,**bold***italic* - CRITICAL: Multiple items in the array are concatenated on a single line, NOT displayed as separate lines!
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):
- : 2 (NOT 3!)
version - : "counter"
widgetType - 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 and simple field reference
"disaggregated": true - Field matches dataset column directly
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": "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 and aggregation expression
"disaggregated": false - CRITICAL: Field MUST match
nameexactly (e.g.,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": "Total Spend"}
},
"frame": {"showTitle": true, "title": "Total Spend"}
}
},
"position": {"x": 0, "y": 0, "width": 2, "height": 3}
}Table:
- : 2 (NOT 1 or 3!)
version - : "table"
widgetType - Columns only need and
fieldName- no other properties!displayName - Use for raw rows
"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": "Name"},
{"fieldName": "value", "displayName": "Value"}
]
},
"frame": {"showTitle": true, "title": "Details"}
}
},
"position": {"x": 0, "y": 0, "width": 6, "height": 6}
}Line / Bar Charts:
- : 3
version - : "line" or "bar"
widgetType - Use ,
x, optionalyencodingscolor - :
scale.type(dates),"temporal"(numbers),"quantitative"(strings)"categorical" - Use with pre-aggregated dataset data
"disaggregated": true
Multiple Lines - Two Approaches:
- Multi-Y Fields (different metrics on same chart):
json
"y": {
"scale": {"type": "quantitative"},
"fields": [
{"fieldName": "sum(orders)", "displayName": "Orders"},
{"fieldName": "sum(returns)", "displayName": "Returns"}
]
}- 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 field - bars stack on top of each other
mark - Grouped: Add - bars side-by-side for comparison
"mark": {"layout": "group"}
Pie Chart:
- : 3
version - : "pie"
widgetType - : quantitative aggregate
angle - : categorical dimension
color - Limit to 3-8 categories for readability
小部件命名规范(重要提示):
- :只能使用字母数字、连字符和下划线(不能有空格、括号、冒号)
widget.name - :人类可读的名称(允许使用任何字符)
frame.title - :始终使用
widget.queries[0].name"main_query"
重要版本要求:
| 小部件类型 | 版本 |
|---|---|
| counter | 2 |
| table | 2 |
| filter-multi-select | 2 |
| filter-single-select | 2 |
| filter-date-range-picker | 2 |
| bar | 3 |
| line | 3 |
| pie | 3 |
| 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):
- :2(不是3!)
version - :"counter"
widgetType - 百分比值在数据中必须是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}
}表格:
- :2(不是1或3!)
version - :"table"
widgetType - 列只需要和
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}
}折线图 / 柱状图:
- :3
version - :"line"或"bar"
widgetType - 使用、
x、可选的y编码color - :
scale.type(日期)、"temporal"(数字)、"quantitative"(字符串)"categorical" - 对于预聚合数据集,使用
"disaggregated": true
多折线图的两种实现方式:
- 多Y字段(同一图表显示不同指标):
json
"y": {
"scale": {"type": "quantitative"},
"fields": [
{"fieldName": "sum(orders)", "displayName": "订单数"},
{"fieldName": "sum(returns)", "displayName": "退货数"}
]
}- 颜色分组(同一指标按维度拆分):
json
"y": {"fieldName": "sum(revenue)", "scale": {"type": "quantitative"}},
"color": {"fieldName": "region", "scale": {"type": "categorical"}, "displayName": "地区"}柱状图模式:
- 堆叠(默认):不设置字段 - 柱状图堆叠显示
mark - 分组:添加- 柱状图并排显示以便对比
"mark": {"layout": "group"}
饼图:
- :3
version - :"pie"
widgetType - :数值型聚合
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-selectfilter-date-range-picker- DO NOT use
- this does not exist and will cause errorswidgetType: "filter"- Filters use
spec.version: 2- ALWAYS include
withframefor filter widgetsshowTitle: true
Filter widget types:
- : for DATE/TIMESTAMP fields
filter-date-range-picker - : categorical with single selection
filter-single-select - : categorical with multiple selections
filter-multi-select
重要提示:过滤器小部件使用的类型与图表不同!
- 有效类型:
、filter-multi-select、filter-single-selectfilter-date-range-picker- 不要使用
- 该类型不存在,会导致错误widgetType: "filter"- 过滤器使用
spec.version: 2- 始终为过滤器小部件添加带
的showTitle: trueframe
过滤器小部件类型:
- :用于DATE/TIMESTAMP字段
filter-date-range-picker - :单选分类过滤器
filter-single-select - :多选分类过滤器
filter-multi-select
Global Filters vs Page-Level Filters
全局过滤器 vs 页面级过滤器
| Type | Placement | Scope | Use Case |
|---|---|---|---|
| Global Filter | Dedicated page with | Affects ALL pages that have datasets with the filter field | Cross-dashboard filtering (e.g., date range, campaign) |
| Page-Level Filter | Regular page with | Affects ONLY widgets on that same page | Page-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:
- Include the filter dimension in datasets for pages that should be filtered
- Exclude the filter dimension from datasets for pages that should NOT be filtered
| 类型 | 位置 | 范围 | 使用场景 |
|---|---|---|---|
| 全局过滤器 | 专用页面,设置 | 影响所有包含该过滤器字段的数据集的页面 | 跨仪表板过滤(例如:日期范围、活动) |
| 页面级过滤器 | 普通页面,设置 | 仅影响同一页面上的小部件 | 页面专属过滤(例如:仅在细分页面上使用平台过滤器) |
关键要点:过滤器仅影响包含该过滤器字段的数据集。要让过滤器仅影响特定页面:
- 在需要被过滤的页面的数据集中包含过滤器维度
- 在不需要被过滤的页面的数据集中排除过滤器维度
Filter Widget Structure
过滤器小部件结构
CRITICAL: Do NOT use- it causes SQL errors! Use a simple field expression instead.associative_filter_predicate_group
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}
}重要提示:不要使用- 会导致SQL错误! 改用简单的字段表达式。associative_filter_predicate_group
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:
- All widget names use only alphanumeric + hyphens + underscores
- All rows sum to width=6 with no gaps
- KPIs use height 3-4, charts use height 5-6
- Chart dimensions have ≤8 distinct values
- All widget fieldNames match dataset columns exactly
- Field in query.fields matches
namein encodings exactly (e.g., bothfieldName)"sum(spend)" - Counter datasets: use for 1-row datasets,
disaggregated: truewith aggregation for multi-rowdisaggregated: false - Percent values are 0-1 (not 0-100)
- SQL uses Spark syntax (date_sub, not INTERVAL)
- All SQL queries tested via and return expected data
execute_sql
部署前,请验证:
- 所有小部件名称仅使用字母数字、连字符和下划线
- 每行的总宽度为6,无间隙
- KPI使用高度3-4,图表使用高度5-6
- 图表维度的不同值≤8个
- 所有小部件的fieldName与数据集列完全匹配
- query.fields中的字段与encodings中的
name完全匹配(例如:均为fieldName)"sum(spend)" - 计数器数据集:1行数据集使用,多行数据集使用
disaggregated: true并配合聚合disaggregated: false - 百分比值为0-1(不是0-100)
- SQL使用Spark语法(date_sub,而非INTERVAL)
- 所有SQL查询已通过测试并返回预期数据
execute_sql
Complete Example
完整示例
python
import jsonpython
import jsonStep 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": ""}],
"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": ""}],
"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": ""}],
"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": ""},
{"name": "trip_count", "expression": ""}
],
"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": ""},
{"name": "trip_count", "expression": ""}
],
"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}
}
]
}]
}
tripsavg_fareavg_distancepickup_ziptrip_countpickup_ziptrip_countdashboard = {
"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": ""}],
"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": ""}],
"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": ""}],
"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": ""},
{"name": "trip_count", "expression": ""}
],
"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": ""},
{"name": "trip_count", "expression": ""}
],
"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}
}
]
}]
}
tripsavg_fareavg_distancepickup_ziptrip_countpickup_ziptrip_countStep 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"])
undefinedresult = 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"])
undefinedComplete Example with Filters
带过滤器的完整示例
python
import jsonpython
import jsonDashboard 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": ""}],
"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": ""}
# 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}
}
]
}
]
}
total_revenueregiondashboard_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": ""}],
"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": ""}
# 不要使用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}
}
]
}
]
}
total_revenueregionDeploy 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"])
undefinedresult = 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"])
undefinedTroubleshooting
故障排除
Widget shows "no selected fields to visualize"
小部件显示“没有选定的可视化字段”
This is a field name mismatch error. The in must exactly match the in .
namequery.fieldsfieldNameencodingsFix: 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.fieldsnameencodingsfieldName修复方法: 确保名称完全匹配:
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 block
spec - Use directly on the widget object
multilineTextboxSpec - Do NOT use - this is invalid
widgetType: "text"
Table widget errors:
- Use (NOT 1 or 3)
version: 2 - Column objects only need and
fieldNamedisplayName - Do NOT add ,
type, or other column propertiesnumberFormat
Counter widget errors:
- Use (NOT 3)
version: 2 - Ensure dataset returns exactly 1 row
检查版本号:
- 计数器:
version: 2 - 表格:
version: 2 - 过滤器:
version: 2 - 柱状图/折线图/饼图:
version: 3
文本小部件错误:
- 文本小部件不能有块
spec - 直接在小部件对象上使用
multilineTextboxSpec - 不要使用- 该类型无效
widgetType: "text"
表格小部件错误:
- 使用(不是1或3)
version: 2 - 列对象只需要和
fieldNamedisplayName - 不要添加、
type或其他列属性numberFormat
计数器小部件错误:
- 使用(不是3)
version: 2 - 确保数据集返回恰好1行
Dashboard shows empty widgets
仪表板显示空小部件
- Run the dataset SQL query directly to check data exists
- Verify column aliases match widget field expressions
- Check flag (should be
disaggregatedfor pre-aggregated data)true
- 直接运行数据集的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 is one of:
widgetType,filter-multi-select,filter-single-selectfilter-date-range-picker - DO NOT use - this is invalid
widgetType: "filter" - Verify is
spec.version2 - Ensure in encodings matches the query
queryNamename - Confirm in filter queries
disaggregated: false - Ensure with
frameis includedshowTitle: true
- 检查是否为以下之一:
widgetType、filter-multi-select、filter-single-selectfilter-date-range-picker - 不要使用- 该类型无效
widgetType: "filter" - 验证为
spec.version2 - 确保encodings中的与查询的
queryName匹配name - 确认过滤器查询中的
disaggregated: false - 确保包含带的
showTitle: trueframe
Filter not affecting expected pages
过滤器未影响预期页面
- Global filters (on page) affect all datasets containing the filter field
PAGE_TYPE_GLOBAL_FILTERS - Page-level filters (on page) only affect widgets on that same page
PAGE_TYPE_CANVAS - 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过滤器显示associative_filter_predicate_group
的“UNRESOLVED_COLUMN”错误
associative_filter_predicate_group- DO NOT use associative_filter_predicate_group`)` in filter queries
COUNT_IF(\ - This internal expression causes SQL errors when the dashboard executes queries
- Use a simple field expression instead: field`"}`
{"name": "field", "expression": "\
- 不要在过滤器查询中使用associative_filter_predicate_group`)`
COUNT_IF(\ - 该内部表达式会导致仪表板执行查询时出现SQL错误
- 改用简单的字段表达式:field`"}`
{"name": "field", "expression": "\
Text widget shows title and description on same line
文本小部件的标题和描述显示在同一行
- Multiple items in the array are concatenated, not displayed on separate lines
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