@clipboard-health/ai-rules 2.29.2 → 2.29.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/metabase-to-hex/SKILL.md +444 -0
- package/skills/metabase-to-hex/references/cbh_stack.md +123 -0
- package/skills/metabase-to-hex/references/gotchas.md +454 -0
- package/skills/metabase-to-hex/references/yaml_schemas.md +281 -0
- package/skills/metabase-to-hex/scripts/audit_vars.sh +26 -0
- package/skills/metabase-to-hex/scripts/build_layout.py +227 -0
- package/skills/metabase-to-hex/scripts/create_cells.sh.tmpl +41 -0
- package/skills/metabase-to-hex/scripts/inventory.py +120 -0
- package/skills/metabase-to-hex/scripts/rename_cells.py +88 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
# Hex / Metabase migration gotchas — extended catalog
|
|
2
|
+
|
|
3
|
+
Each entry: the symptom, the cause, the fix, and (where useful) the incident it
|
|
4
|
+
came from. SKILL.md has short versions of the most critical ones — this file is
|
|
5
|
+
the long-form reference.
|
|
6
|
+
|
|
7
|
+
## 1. "Undefined variable(s): X" at SQL cell run time
|
|
8
|
+
|
|
9
|
+
**Symptom:** A SQL cell errors with `Undefined variable(s): facility_id` (or
|
|
10
|
+
similar) when the project is run, even though an INPUT cell with that exact
|
|
11
|
+
`config.name` exists in the same project.
|
|
12
|
+
|
|
13
|
+
**Cause:** Hex's Jinja resolution walks the notebook top-to-bottom. If an INPUT
|
|
14
|
+
cell is placed AFTER the SQL cell in the `cells:` list, the variable is not yet
|
|
15
|
+
in scope when the SQL cell renders.
|
|
16
|
+
|
|
17
|
+
**Fix:** Stable-sort `doc['cells']` before importing so INPUT cells come first,
|
|
18
|
+
then MARKDOWN, then SQL:
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
priority = {"INPUT": 0, "MARKDOWN": 1, "SQL": 2}
|
|
22
|
+
doc["cells"].sort(key=lambda c: priority.get(c.get("cellType"), 3))
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Incident:** Dashboard 1242 migration (2026-05-14). After all SQL cells were
|
|
26
|
+
created and inputs were added at the bottom of the YAML, every SQL cell reported
|
|
27
|
+
"Undefined variable(s)". Re-ordering fixed it without changing any cell content.
|
|
28
|
+
|
|
29
|
+
## 2. Jinja variable from another project leaks in
|
|
30
|
+
|
|
31
|
+
**Symptom:** Same as gotcha 1 (`Undefined variable(s): worker_id`), but you've
|
|
32
|
+
already confirmed cell order is correct.
|
|
33
|
+
|
|
34
|
+
**Cause:** SQL was copied from another Hex project whose inputs had different
|
|
35
|
+
names. The reused SQL still references the old names. Common offenders when
|
|
36
|
+
reusing from the WOPs Tool project: `worker_id`, `workplace_id`, `workplace_name`,
|
|
37
|
+
`msa`, `parent_name`, `referrer_id`, `referred_id`, `message_type`.
|
|
38
|
+
|
|
39
|
+
**Fix:** Audit before importing:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
bash scripts/audit_vars.sh hex_cells/
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Cross-check the output against the new project's Input `config.name` values.
|
|
46
|
+
Rename `worker_id → hcp_id`, `workplace_id → facility_id`, etc., as needed. Drop
|
|
47
|
+
filters for inputs that don't exist in the new project at all.
|
|
48
|
+
|
|
49
|
+
**Incident:** Dashboard 1242 migration — 13 of 20 SQL cells were copied from
|
|
50
|
+
WOPs Tool v3 SQL. 11 of them referenced `worker_id` / `workplace_id` / others
|
|
51
|
+
that don't exist in dashboard 1242's input set, which has just 5 params:
|
|
52
|
+
`shift_id, hcp_id, facility_id, referred_id, referrer_id`.
|
|
53
|
+
|
|
54
|
+
## 3. INPUT cell rejected as "malformed"
|
|
55
|
+
|
|
56
|
+
**Symptom:** `hex project import` fails with `A request to the Hex API was
|
|
57
|
+
rejected as malformed. Your input may be invalid, or your CLI version may be out
|
|
58
|
+
of date.` Generic, no row/column pointer.
|
|
59
|
+
|
|
60
|
+
**Cause:** Your INPUT cell `config:` block is missing the required `options: null`
|
|
61
|
+
key, or you added a `label:` key inside `config` that Hex doesn't accept.
|
|
62
|
+
|
|
63
|
+
**Fix:** Use exactly:
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
- cellType: INPUT
|
|
67
|
+
cellId: <uuid7>
|
|
68
|
+
cellLabel: Shift ID
|
|
69
|
+
config:
|
|
70
|
+
inputType: TEXT_INPUT
|
|
71
|
+
name: shift_id
|
|
72
|
+
outputType: STRING
|
|
73
|
+
options: null
|
|
74
|
+
defaultValue: ""
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Bisect technique** (if you can't tell what's wrong): export a known-good YAML
|
|
78
|
+
(WOPs Tool: `019e08cb-79c0-7000-84c7-003f187d2669`), `grep -A 5 "cellType: INPUT"`,
|
|
79
|
+
and copy the exact shape.
|
|
80
|
+
|
|
81
|
+
## 4. `'{{ shift_id }}'` returns no rows (silent failure)
|
|
82
|
+
|
|
83
|
+
**Symptom:** A SQL cell runs without errors but returns 0 rows even when you know
|
|
84
|
+
the data exists. Inspecting Snowflake query history shows it ran with `?`
|
|
85
|
+
placeholder, not the actual value.
|
|
86
|
+
|
|
87
|
+
**Cause:** You wrapped the Jinja variable in manual single quotes. Hex's Jinja
|
|
88
|
+
substitutes string inputs as **already-quoted** values — manual quotes turn the
|
|
89
|
+
substitution into a Snowflake bind parameter literal `?`, which matches nothing.
|
|
90
|
+
|
|
91
|
+
**Fix:** Always write `WHERE col = {{ shift_id }}` (no quotes). Hex emits
|
|
92
|
+
`WHERE col = '66f5bfa6e01fd3697b175ebc'`.
|
|
93
|
+
|
|
94
|
+
For Metabase optional clauses `[[AND col = {{var}}]]`:
|
|
95
|
+
|
|
96
|
+
- Translate to `{% if var %}AND col = {{ var }}{% endif %}`
|
|
97
|
+
- NOT to `{% if var %}AND col = '{{ var }}'{% endif %}`
|
|
98
|
+
|
|
99
|
+
## 5. cellLabel rename silently ignored
|
|
100
|
+
|
|
101
|
+
**Symptom:** You change `cellLabel: Foo` to `cellLabel: Bar` in the YAML, import,
|
|
102
|
+
and the cell still shows "Foo" in the UI and on `hex cell list`.
|
|
103
|
+
|
|
104
|
+
**Cause:** Hex treats `cellLabel` as part of the cell's identity for import
|
|
105
|
+
purposes. Changing it in-place is ignored. Even adding a no-op change to `source`
|
|
106
|
+
doesn't help.
|
|
107
|
+
|
|
108
|
+
**Fix:** Use the cellId-swap pattern — assign a fresh UUID v7, drop the old
|
|
109
|
+
entry, and rewrite every appLayout reference. See `scripts/rename_cells.py`.
|
|
110
|
+
|
|
111
|
+
**Why this works:** From Hex's perspective, the old cellId no longer appears in
|
|
112
|
+
the imported `cells:` list, so it's deleted. The new cellId appears for the
|
|
113
|
+
first time, so it's created with the new label. The appLayout points at the new
|
|
114
|
+
ID, so the UI shows the renamed cell in the right spot.
|
|
115
|
+
|
|
116
|
+
## 6. `hex cell update` returns "Forbidden" on a cell that exists
|
|
117
|
+
|
|
118
|
+
**Symptom:** `hex cell update <cell-id> -t sql -s "..."` fails with `Forbidden`,
|
|
119
|
+
but the cell visibly exists in the project.
|
|
120
|
+
|
|
121
|
+
**Cause:** Hex tracks every cell with two IDs and the update endpoint accepts
|
|
122
|
+
only one of them:
|
|
123
|
+
|
|
124
|
+
- **`staticId`** — the canonical ID. Returned in the `staticId` field of
|
|
125
|
+
`hex cell create`'s JSON response, and shown as `cellId:` in `hex project export`.
|
|
126
|
+
Stable across imports. **NOT accepted by `hex cell update`.**
|
|
127
|
+
- **`id`** (the dynamic id) — the per-version handle. Returned in the `id` field
|
|
128
|
+
of `hex cell create` and listed by `hex cell list`. Changes on every YAML
|
|
129
|
+
import. **This is the one `hex cell update` requires.**
|
|
130
|
+
|
|
131
|
+
The `Forbidden` response when you pass a `staticId` (or a stale dynamic id from
|
|
132
|
+
before the last import) is misleading — it's effectively a 404.
|
|
133
|
+
|
|
134
|
+
**Fix:** Always `hex cell list <project-id> --json` immediately before updating
|
|
135
|
+
to fetch fresh dynamic ids. The dynamic id from a `hex cell create` call also
|
|
136
|
+
goes stale after any subsequent YAML import — re-list, then update.
|
|
137
|
+
|
|
138
|
+
**Incident:** Generalized supply drilldown migration (2026-05-26). After
|
|
139
|
+
creating SQL cells with `hex cell create` and capturing their `staticId`
|
|
140
|
+
values, a later YAML import (to add a markdown header and dropdown input)
|
|
141
|
+
invalidated the dynamic ids. Subsequent `hex cell update <staticId>` calls
|
|
142
|
+
returned `Forbidden`; switching to `hex cell list --json` ids worked
|
|
143
|
+
immediately.
|
|
144
|
+
|
|
145
|
+
## 7. `hex cell list` returns only ~25 cells
|
|
146
|
+
|
|
147
|
+
**Symptom:** A project has 39 cells but `hex cell list` only shows 20–25, with
|
|
148
|
+
some labels appearing twice.
|
|
149
|
+
|
|
150
|
+
**Cause:** Pagination. The CLI's `--json` output is a single page.
|
|
151
|
+
|
|
152
|
+
**Fix:** Use `hex project export <project-id> -o /tmp/x.yaml` and read `cells:`
|
|
153
|
+
from the YAML. That's the complete list.
|
|
154
|
+
|
|
155
|
+
## 8. Em-dash / unicode in SQL or markdown breaks import
|
|
156
|
+
|
|
157
|
+
**Symptom:** `hex project import` fails with a vague parse error after you edit
|
|
158
|
+
the YAML with a script.
|
|
159
|
+
|
|
160
|
+
**Cause:** Default `yaml.safe_dump` escapes non-ASCII as `—` etc., or wraps
|
|
161
|
+
long lines. Hex's YAML parser doesn't handle either well.
|
|
162
|
+
|
|
163
|
+
**Fix:** Always use:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
yaml.safe_dump(doc, f, allow_unicode=True, width=4096, sort_keys=False)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 9. `metabase-server.execute_card` rejects the parameters argument
|
|
170
|
+
|
|
171
|
+
**Symptom:** Call to `mcp__metabase-server__execute_card` returns
|
|
172
|
+
`Assert failed: (u/maybe? sequential? parameters)`.
|
|
173
|
+
|
|
174
|
+
**Cause:** The MCP schema says `parameters` is an object, but the underlying
|
|
175
|
+
Metabase server expects a list. Sending `{}` triggers the assert.
|
|
176
|
+
|
|
177
|
+
**Fix:** Pass `[]` (an empty list) when the schema requires a value. If the MCP
|
|
178
|
+
client lets you omit it, omit it.
|
|
179
|
+
|
|
180
|
+
## 10. metabase-cognitionai returns 401 across all endpoints
|
|
181
|
+
|
|
182
|
+
**Symptom:** Every call to any `mcp__metabase-cognitionai__*` tool returns 401,
|
|
183
|
+
even though the connector UI says "connected."
|
|
184
|
+
|
|
185
|
+
**Cause:** Server-side credential refresh hiccup. Not user-fixable.
|
|
186
|
+
|
|
187
|
+
**Fix:** Wait a few minutes — it often self-resolves. As fallback, use
|
|
188
|
+
`mcp__metabase-server__*` (less rich, but works).
|
|
189
|
+
|
|
190
|
+
## 11. `metabase-server.get_dashboard_cards` Zod validation error
|
|
191
|
+
|
|
192
|
+
**Symptom:** Calling `mcp__metabase-server__get_dashboard_cards` returns a Zod
|
|
193
|
+
union validation failure on the first content block.
|
|
194
|
+
|
|
195
|
+
**Cause:** Known schema bug in the MCP for some dashboards.
|
|
196
|
+
|
|
197
|
+
**Fix:** Use `mcp__metabase-cognitionai__get_dashboard` instead, then parse the
|
|
198
|
+
`dashcards` field from the returned JSON yourself.
|
|
199
|
+
|
|
200
|
+
## 12. Empty SQL results when running cells as alex
|
|
201
|
+
|
|
202
|
+
**Symptom:** SQL cells run successfully but return zero rows even for queries
|
|
203
|
+
you've verified return data elsewhere.
|
|
204
|
+
|
|
205
|
+
**Cause:** The cell's `dataConnectionId` is the `Hex Apps: Snowflake PII Service
|
|
206
|
+
Account` connection (`019bb2d8-d867-7001-bb8a-ccb8b6d16e8a`). Workspace member
|
|
207
|
+
alex isn't in the gating group "Hex Apps: Snowflake App PII Service Account", so
|
|
208
|
+
they only get `VIEW_RESULTS` — running queries silently returns empty.
|
|
209
|
+
|
|
210
|
+
**Fix:** Use `snowflake analytics` (`530b70b8-b300-43c9-9b3e-e4b98ded0379`)
|
|
211
|
+
for draft work. Only switch to the PII service account at publish time, when the
|
|
212
|
+
app runs queries server-side as the service account.
|
|
213
|
+
|
|
214
|
+
## 13. CLI does not surface per-cell errors
|
|
215
|
+
|
|
216
|
+
**Symptom:** `hex project run <id>` returns a `RUNNING` or `SUCCEEDED` status,
|
|
217
|
+
but cells are visibly broken in the UI.
|
|
218
|
+
|
|
219
|
+
**Cause:** The CLI's run endpoint reports project-level status only. Per-cell
|
|
220
|
+
errors (Jinja undefined, SQL syntax, missing columns) are surfaced only in the
|
|
221
|
+
notebook UI.
|
|
222
|
+
|
|
223
|
+
**Fix:** After `hex project run`, open the project URL in the browser and click
|
|
224
|
+
through each tab. Or run the SQL manually in Snowflake (with substituted
|
|
225
|
+
variable values) before pushing to validate.
|
|
226
|
+
|
|
227
|
+
## 14. Guessed column names in copied SQL
|
|
228
|
+
|
|
229
|
+
**Symptom:** A SQL cell errors with `SQL compilation error: invalid identifier`.
|
|
230
|
+
|
|
231
|
+
**Cause:** SQL was reused from a previous migration where the column existed in
|
|
232
|
+
a different (often legacy) table. The column doesn't exist in the new core
|
|
233
|
+
model, e.g., `PSST_BALANCE_HOURS` or `PSST_DAYS_ACCRUED` on `FCT_SHIFTS`.
|
|
234
|
+
|
|
235
|
+
**Fix:** Always verify columns via `INFORMATION_SCHEMA.COLUMNS` before reusing
|
|
236
|
+
SQL across schemas:
|
|
237
|
+
|
|
238
|
+
```sql
|
|
239
|
+
SELECT COLUMN_NAME, DATA_TYPE
|
|
240
|
+
FROM ANALYTICS.INFORMATION_SCHEMA.COLUMNS
|
|
241
|
+
WHERE TABLE_SCHEMA = 'DBT_PRODUCTION_CORE'
|
|
242
|
+
AND TABLE_NAME = '<new table>'
|
|
243
|
+
ORDER BY ORDINAL_POSITION;
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
When a column truly doesn't exist in core, either derive it from available
|
|
247
|
+
columns (e.g., `HOURS_LAST_90D` from `SUM(PAY_DURATION_IN_MINUTES) / 60.0` on
|
|
248
|
+
`FCT_SHIFTS`) or leave a `-- TODO` comment for the user.
|
|
249
|
+
|
|
250
|
+
## 15. App view stays blank after YAML import — needs UI bootstrap
|
|
251
|
+
|
|
252
|
+
**Symptom:** You imported a YAML with a well-formed `appLayout` block (cells
|
|
253
|
+
referenced, all cellIds resolve, structurally identical to a known-working
|
|
254
|
+
dashboard). `hex project run` completes successfully. The Logic view
|
|
255
|
+
(`/draft/logic`) shows all cells. But the App view (`?view=app` or `/draft/app`)
|
|
256
|
+
is completely blank — no inputs, no markdown, no charts, no error message, no
|
|
257
|
+
"Add cell" placeholder. Hard refresh, incognito, different browser — still
|
|
258
|
+
blank.
|
|
259
|
+
|
|
260
|
+
**Cause:** `hex project import` can only _update_ an existing App canvas. It
|
|
261
|
+
cannot bootstrap one from scratch. If no human has ever opened the Hex App
|
|
262
|
+
builder and dragged at least one cell onto the canvas, the project doesn't yet
|
|
263
|
+
have a draft-app state, and every subsequent `appLayout` import is silently
|
|
264
|
+
no-op'd on the App-view side. `hex project export` will faithfully round-trip
|
|
265
|
+
the `appLayout` block, making it look like it was applied.
|
|
266
|
+
|
|
267
|
+
**Confirmed by experiment** (dashboard 2769 migration, 2026-05-15):
|
|
268
|
+
|
|
269
|
+
1. Built `appLayout` via YAML import in the new project — App view blank.
|
|
270
|
+
2. Forced reset (imported empty `appLayout`, re-exported, re-applied) — still
|
|
271
|
+
blank.
|
|
272
|
+
3. Tried COLLAPSIBLE-wrapped structure mirroring WOPs Tool — 15 SQL cells
|
|
273
|
+
broke because Jinja can't resolve across COLLAPSIBLE boundaries. Reverted.
|
|
274
|
+
4. Created a fresh v2 project from the same YAML — still blank.
|
|
275
|
+
5. Cloned the Payments Team Dashboard YAML byte-for-byte into a fresh project
|
|
276
|
+
(since the skill listed Payments as a reference) — also blank.
|
|
277
|
+
6. User checked the _original_ Payments dashboard's App view — blank too. The
|
|
278
|
+
skill's claim that Payments had a working App view was wrong; only the
|
|
279
|
+
Logic view ever worked.
|
|
280
|
+
7. User confirmed only the WOPs Tool's App view actually renders.
|
|
281
|
+
8. Reviewed the WOPs Tool migration transcript: the user manually dragged
|
|
282
|
+
INPUT cells onto the canvas first, **then** `hex project import` added the
|
|
283
|
+
remaining 13 SQL cells. The YAML-only path was never used on a virgin
|
|
284
|
+
project.
|
|
285
|
+
|
|
286
|
+
**Fix:** Don't try to bootstrap the App view from YAML. After Phase 6a (inputs
|
|
287
|
+
/ markdown imported), stop and ask the user to:
|
|
288
|
+
|
|
289
|
+
> Open `https://app.hex.tech/<workspace>/hex/<project-id>/draft/logic?view=app`
|
|
290
|
+
> and drag any one cell from the cell tray onto the canvas. That single action
|
|
291
|
+
> initializes Hex's draft-app state. Let me know once it's done and I'll
|
|
292
|
+
> re-import the layout to populate the rest.
|
|
293
|
+
|
|
294
|
+
The cell they drag doesn't need to be in its final position — Phase 6c
|
|
295
|
+
overwrites the canvas with the real layout. After the user confirms, re-export
|
|
296
|
+
the project, build the real `appLayout`, and re-import. Hex now treats the
|
|
297
|
+
import as an update to an initialized canvas and applies it.
|
|
298
|
+
|
|
299
|
+
**Diagnostic checklist** when you see a blank App view:
|
|
300
|
+
|
|
301
|
+
- Has the user ever opened `?view=app` and interacted with the canvas?
|
|
302
|
+
If no → this is the gotcha. Don't waste time diffing YAML structures.
|
|
303
|
+
- Does `hex project export` show the `appLayout` you imported? If yes → that
|
|
304
|
+
confirms the import "succeeded" structurally but Hex isn't applying it to
|
|
305
|
+
the App view (this is the expected mode of failure).
|
|
306
|
+
- Does the Logic view show the cells? If yes → cells are fine, it's purely a
|
|
307
|
+
layout problem.
|
|
308
|
+
|
|
309
|
+
**Why this isn't surfaced by the CLI:** `hex project import` returns a success
|
|
310
|
+
exit code regardless. There's no warning or error about the App view not being
|
|
311
|
+
initialized.
|
|
312
|
+
|
|
313
|
+
## 16. `| array` Jinja filter required for multi-select inputs in SQL
|
|
314
|
+
|
|
315
|
+
**Symptom:** A SQL cell that references a multi-select dropdown's variable
|
|
316
|
+
errors at run time with:
|
|
317
|
+
|
|
318
|
+
```text
|
|
319
|
+
MissingArrayFilterError: Got a list, tuple, or set.
|
|
320
|
+
Did you forget to apply '| array' to your query?
|
|
321
|
+
For example: `{{ list_of_values | array }}`
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**Cause:** Hex auto-quotes scalar string inputs into the SQL substitution slot
|
|
325
|
+
(see gotcha 4) but refuses to silently choose a serialization for list-typed
|
|
326
|
+
inputs. The developer must opt in to the `array` filter, which expands the list
|
|
327
|
+
to comma-separated quoted scalars — e.g. `'A', 'B', 'C'`.
|
|
328
|
+
|
|
329
|
+
**Fix:** Use the filter inside an `IN (...)` clause:
|
|
330
|
+
|
|
331
|
+
```sql
|
|
332
|
+
WHERE w.MSA IN ({{ msa_list | array }})
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
This is the same `{{ var }}` slot gotcha 4 says _not_ to quote — the parens
|
|
336
|
+
belong to the SQL (`IN (...)`), not around the Jinja. The `| array` filter is a
|
|
337
|
+
no-op for single-select scalars, so the SQL stays forward-compatible if the
|
|
338
|
+
input later gets toggled between single- and multi-select.
|
|
339
|
+
|
|
340
|
+
**Edge — empty selection:** `{% if msa_list %}…{% else %}AND 1=0{% endif %}`
|
|
341
|
+
still works. Jinja treats both `None` and an empty list `[]` as falsy, so the
|
|
342
|
+
guard branch fires and the query short-circuits to zero rows rather than
|
|
343
|
+
scanning unfiltered.
|
|
344
|
+
|
|
345
|
+
**Incident:** Generalized supply drilldown (`019e64b0-a730-7000-807c-5debd510361e`,
|
|
346
|
+
2026-05-26). After the user flipped a `DROPDOWN` input to multi-select in the
|
|
347
|
+
Hex UI, both downstream SQL cells errored with `MissingArrayFilterError` on the
|
|
348
|
+
next run. Adding `| array` fixed both cells; no other change needed.
|
|
349
|
+
|
|
350
|
+
## 17. YAML import accepts a narrow INPUT subset — multi-select must be flipped in the UI
|
|
351
|
+
|
|
352
|
+
**Symptom:** `hex project import` rejects a YAML containing an INPUT cell
|
|
353
|
+
whose `inputType` is anything beyond a small allowed list. The error is the
|
|
354
|
+
same generic "rejected as malformed" from gotcha 3 — no field-level detail.
|
|
355
|
+
|
|
356
|
+
**Cause:** The Hex CLI's import endpoint only wires up a subset of the full
|
|
357
|
+
INPUT schema. Multi-select, date-picker, and file-picker exist in the product
|
|
358
|
+
but the import API isn't connected to them.
|
|
359
|
+
|
|
360
|
+
**Works via YAML import:**
|
|
361
|
+
|
|
362
|
+
- `TEXT_INPUT` (outputType `STRING`, `options: null` — gotcha 3)
|
|
363
|
+
- Single-select `DROPDOWN` backed by a dataframe column:
|
|
364
|
+
|
|
365
|
+
```yaml
|
|
366
|
+
inputType: DROPDOWN
|
|
367
|
+
name: <var>
|
|
368
|
+
outputType: DYNAMIC
|
|
369
|
+
options:
|
|
370
|
+
valueOptions: { dfName: <sql_cell_dataframe>, variableName: null }
|
|
371
|
+
optional: false
|
|
372
|
+
defaultValue: null
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Does NOT work via YAML import** (all "malformed" — confirmed 2026-05-26 and
|
|
376
|
+
re-confirmed for date variants 2026-06-01):
|
|
377
|
+
|
|
378
|
+
- `MULTI_SELECT`
|
|
379
|
+
- `MULTI_SELECT_INPUT`
|
|
380
|
+
- `DROPDOWN` + `multiSelect: true` or `multipleSelect: true`
|
|
381
|
+
- `DATE_PICKER` (outputType `DATETIME`)
|
|
382
|
+
- `DATE_INPUT`, `DATE` (outputType `DATETIME`)
|
|
383
|
+
- `DATE_RANGE`, `DATE_RANGE_PICKER`, `DATERANGE` (outputType `DATE_RANGE`)
|
|
384
|
+
- `FILE_INPUT` (outputType `FILE`)
|
|
385
|
+
|
|
386
|
+
**Pattern for multi-select needs:** push a single-select `DROPDOWN` via YAML
|
|
387
|
+
(the supported shape above), then stop and ask the user to flip the multi-select
|
|
388
|
+
toggle in the cell's right-sidebar config. SQL `WHERE col IN ({{ var | array }})`
|
|
389
|
+
works for both single and multi outputs (the `| array` filter is a no-op for
|
|
390
|
+
scalars — see gotcha 16), so the SQL doesn't need to change when the toggle
|
|
391
|
+
flips. See the SKILL.md Phase 6a sub-pattern for the full workflow.
|
|
392
|
+
|
|
393
|
+
**Pattern for date / date-range / file-input needs:** push a `TEXT_INPUT`
|
|
394
|
+
placeholder with the snake_case `name` the SQL will reference, ask the user
|
|
395
|
+
to change **Input type** in the right sidebar (and toggle **Allow date range**
|
|
396
|
+
for ranges), then update SQL to the post-flip access pattern. See gotcha 18
|
|
397
|
+
for the date-range variable shape and `yaml_schemas.md` for the resulting
|
|
398
|
+
export YAML. Pushing as plain `TEXT_INPUT` with `::timestamp_ntz` casting still
|
|
399
|
+
works fine if the user doesn't need calendar UX.
|
|
400
|
+
|
|
401
|
+
## 18. Date range input — Hex injects two separate variables, `<name>_start` and `<name>_end`
|
|
402
|
+
|
|
403
|
+
**Symptom:** SQL cell errors with `Undefined variable(s): <base name>` (e.g.
|
|
404
|
+
`Undefined variable(s): shift_start_range`) even though the date-range INPUT
|
|
405
|
+
cell exists, has a default value set, and is ordered above the SQL cells in
|
|
406
|
+
the project. Re-importing the YAML, re-running, and refreshing the App view
|
|
407
|
+
don't help.
|
|
408
|
+
|
|
409
|
+
**Cause:** With `options.useDateRange: true` set on a date input, Hex does
|
|
410
|
+
**not** bind the cell's `name` field to any variable. Instead it injects two
|
|
411
|
+
new variables suffixed `_start` and `_end`, both `datetime.date`. The base
|
|
412
|
+
`name` (e.g. `shift_start_range`) is literally not in scope, so any reference
|
|
413
|
+
to it — including `{{ var.start }}`, `{{ var[0] }}`, `{{ var.from_date }}`,
|
|
414
|
+
`{% if var %}` — fails with `Undefined variable(s): <base name>`. The error
|
|
415
|
+
message is honest, not misleading: the base name truly does not exist.
|
|
416
|
+
|
|
417
|
+
**Fix:**
|
|
418
|
+
|
|
419
|
+
```sql
|
|
420
|
+
{% if shift_start_range_start %}AND SHIFT_START_AT >= CAST({{ shift_start_range_start }} AS TIMESTAMP_NTZ){% endif %}
|
|
421
|
+
{% if shift_start_range_end %}AND SHIFT_START_AT < DATEADD(day, 1, CAST({{ shift_start_range_end }} AS TIMESTAMP_NTZ)){% endif %}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Notes:
|
|
425
|
+
|
|
426
|
+
- Guard each bound separately. The user can clear one without the other.
|
|
427
|
+
- `+1 day` on the upper bound makes the range inclusive of the to-date (Hex
|
|
428
|
+
passes dates at midnight UTC, so `< to_date` excludes the entire to-day).
|
|
429
|
+
- The variable type is `datetime.date`, not `datetime.datetime`. Snowflake's
|
|
430
|
+
`CAST(... AS TIMESTAMP_NTZ)` handles either.
|
|
431
|
+
- For a _single_ date input (`useDateRange: false`), the variable IS bound to
|
|
432
|
+
the cell's `name` — i.e. `{{ shift_start_range }}` works there. Only the
|
|
433
|
+
range mode rewires the name into `_start`/`_end` suffixes.
|
|
434
|
+
|
|
435
|
+
**Diagnostic recipe.** When you hit `Undefined variable(s): X` and you can
|
|
436
|
+
confirm the input cell defining `X` exists and is above the SQL cells, the
|
|
437
|
+
fastest resolution is a one-off Python cell:
|
|
438
|
+
|
|
439
|
+
```python
|
|
440
|
+
for name in ['shift_start_range', 'shift_start_range_start', 'shift_start_range_end',
|
|
441
|
+
'shift_start_range_from', 'shift_start_range_to']:
|
|
442
|
+
val = globals().get(name, '<not in globals>')
|
|
443
|
+
print(f"{name}: type={type(val).__name__} value={val!r}")
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
Run the project, read the output, fix SQL, delete the cell. Beats guessing.
|
|
447
|
+
|
|
448
|
+
**Incident:** card 21241 migration (project `019e83b9-aed0-723b-9b96-313269b324bc`,
|
|
449
|
+
2026-06-01). After the user flipped a `TEXT_INPUT` to a date-range input in
|
|
450
|
+
the Hex UI, the project ran with `Undefined variable(s): shift_start_range`.
|
|
451
|
+
Wrong attribute patterns I cycled through before adding the diagnostic cell:
|
|
452
|
+
`.start`/`.end` (web search suggested), `[0]`/`[1]` (Python list convention).
|
|
453
|
+
The diagnostic showed `shift_start_range_start` and `shift_start_range_end`
|
|
454
|
+
as the actual names; switching to those resolved both SQL cells.
|