close issue 0081: tables promoted to registry + fn doctor cpp-apps BeginTable check
- docs/TQL.md: añadidas secciones joins, views, main_source, 24 viz tokens completos
(extraidos de tql_helpers.cpp), color_rules, fn.* builtins completos (20 funciones),
funciones bloqueadas del sandbox, tabla de estado de implementacion actualizada.
Nota al pie referencia los 129 checks roundtrip (41 emit + 88 apply).
- functions/infra/audit_cpp_apps.go: añadida AuditCppTableMigration() que escanea
.cpp de cada app imgui buscando ImGui::BeginTable; status CANDIDATE/MIXED/clean
segun si usa data_table_cpp_viz en uses_functions.
- cmd/fn/doctor.go: fn doctor cpp-apps ahora incluye seccion BeginTable migration
con tabwriter CANDIDATE/MIXED; --json produce {conformance, table_migration}.
doctorAll incluye cpp_table_migration en el mapa JSON.
- .claude/rules/fn_doctor.md: tabla de subcomandos y acciones complementarias
actualizadas con el nuevo check.
- dev/issues/0081 movido a completed/ con status done y notas de deuda documentadas.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,10 +20,18 @@ fn doctor sync # Solo drift pc_locations BD vs disco local
|
|||||||
fn doctor uses-functions # Solo audit imports reales vs uses_functions
|
fn doctor uses-functions # Solo audit imports reales vs uses_functions
|
||||||
fn doctor unused # Solo funciones huerfanas del registry
|
fn doctor unused # Solo funciones huerfanas del registry
|
||||||
fn doctor cpp-apps # Conformidad C++ con cpp/PATTERNS.md (cfg.about/log, no app_menubar manual, no DockSpace duplicado)
|
fn doctor cpp-apps # Conformidad C++ con cpp/PATTERNS.md (cfg.about/log, no app_menubar manual, no DockSpace duplicado)
|
||||||
|
# + check BeginTable inline: CANDIDATE (no migrado) / MIXED (parcial) / silencio (limpio)
|
||||||
|
|
||||||
fn doctor --json # Salida JSON (cualquier subcomando) — para agentes/scripts
|
fn doctor --json # Salida JSON (cualquier subcomando) — para agentes/scripts
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`fn doctor cpp-apps` produce dos secciones:
|
||||||
|
1. Conformance (cfg.about/log, fn::run_app, menubar, DockSpace) — una fila por app imgui.
|
||||||
|
2. BeginTable migration (issue 0081) — solo apps con `ImGui::BeginTable` inline:
|
||||||
|
- `CANDIDATE`: N tablas inline sin `data_table_cpp_viz` en uses_functions. Considerar migracion.
|
||||||
|
- `MIXED`: N tablas inline con `data_table_cpp_viz` ya declarado. Migracion parcial OK.
|
||||||
|
- silencio: 0 BeginTable inline (limpio o completamente migrado).
|
||||||
|
|
||||||
### Mapeo subcomando → funcion del registry
|
### Mapeo subcomando → funcion del registry
|
||||||
|
|
||||||
| Subcomando | Funcion |
|
| Subcomando | Funcion |
|
||||||
@@ -33,7 +41,8 @@ fn doctor --json # Salida JSON (cualquier subcomando) — para agentes
|
|||||||
| `sync` | `pc_locations_drift_go_infra` |
|
| `sync` | `pc_locations_drift_go_infra` |
|
||||||
| `uses-functions` | `audit_uses_functions_go_infra` |
|
| `uses-functions` | `audit_uses_functions_go_infra` |
|
||||||
| `unused` | `find_unused_functions_go_infra` |
|
| `unused` | `find_unused_functions_go_infra` |
|
||||||
| `cpp-apps` | `audit_cpp_apps_go_infra` |
|
| `cpp-apps` (conformance) | `audit_cpp_apps_go_infra` |
|
||||||
|
| `cpp-apps` (table migration) | `audit_cpp_table_migration_go_infra` (inline en `audit_cpp_apps.go`) |
|
||||||
|
|
||||||
Cada subcomando es un wrapper fino. Toda la logica vive en la funcion. Si quieres usar la salida en otro programa Go, importa la funcion directamente.
|
Cada subcomando es un wrapper fino. Toda la logica vive en la funcion. Si quieres usar la salida en otro programa Go, importa la funcion directamente.
|
||||||
|
|
||||||
@@ -64,6 +73,8 @@ Texto humano por defecto (tabwriter). `--json` produce array/objeto serializable
|
|||||||
| `manual_DockSpaceOverViewport_*` | Borrar la llamada o setear `cfg.auto_dockspace = false` si la app gestiona docking propio |
|
| `manual_DockSpaceOverViewport_*` | Borrar la llamada o setear `cfg.auto_dockspace = false` si la app gestiona docking propio |
|
||||||
| `missing_cfg_about` / `missing_cfg_log` | Anadir `cfg.about = {...}` / `cfg.log = {"<name>.log", 1}` antes de `fn::run_app` |
|
| `missing_cfg_about` / `missing_cfg_log` | Anadir `cfg.about = {...}` / `cfg.log = {"<name>.log", 1}` antes de `fn::run_app` |
|
||||||
| `app.md_missing_*` | Regenerar via plantilla del scaffolder (`/new-cpp-app`) o anadir campos a mano |
|
| `app.md_missing_*` | Regenerar via plantilla del scaffolder (`/new-cpp-app`) o anadir campos a mano |
|
||||||
|
| cpp-apps BeginTable `CANDIDATE` | App tiene N `ImGui::BeginTable` sin migrar. Abrir rama TBD, reemplazar tablas por `data_table::render()` via `fn_table_viz`, añadir `data_table_cpp_viz` a `uses_functions` en `app.md` |
|
||||||
|
| cpp-apps BeginTable `MIXED` | Migracion parcial en curso. Continuar wave por wave hasta que no queden BeginTable inline |
|
||||||
| Backup viejo | `backup_all_bash_pipelines ~/backups/fn_registry` |
|
| Backup viejo | `backup_all_bash_pipelines ~/backups/fn_registry` |
|
||||||
|
|
||||||
### Para agentes
|
### Para agentes
|
||||||
|
|||||||
+42
-1
@@ -123,6 +123,11 @@ func doctorAll(root string, jsonOut bool) {
|
|||||||
} else {
|
} else {
|
||||||
all["cpp_apps_error"] = err.Error()
|
all["cpp_apps_error"] = err.Error()
|
||||||
}
|
}
|
||||||
|
if v, err := infra.AuditCppTableMigration(root); err == nil {
|
||||||
|
all["cpp_table_migration"] = v
|
||||||
|
} else {
|
||||||
|
all["cpp_table_migration_error"] = err.Error()
|
||||||
|
}
|
||||||
if v, err := infra.AuditMlEnv(root); err == nil {
|
if v, err := infra.AuditMlEnv(root); err == nil {
|
||||||
all["ml"] = v
|
all["ml"] = v
|
||||||
} else {
|
} else {
|
||||||
@@ -168,10 +173,21 @@ func doctorCppApps(root string, jsonOut bool) {
|
|||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
tableAudits, err2 := infra.AuditCppTableMigration(root)
|
||||||
|
if err2 != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "warning: table migration audit failed: %v\n", err2)
|
||||||
|
tableAudits = nil
|
||||||
|
}
|
||||||
|
|
||||||
if jsonOut {
|
if jsonOut {
|
||||||
emit(audits)
|
emit(map[string]any{
|
||||||
|
"conformance": audits,
|
||||||
|
"table_migration": tableAudits,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Conformance section.
|
||||||
bad := 0
|
bad := 0
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||||
fmt.Fprintln(w, "STATUS\tAPP\tISSUES")
|
fmt.Fprintln(w, "STATUS\tAPP\tISSUES")
|
||||||
@@ -187,6 +203,31 @@ func doctorCppApps(root string, jsonOut bool) {
|
|||||||
}
|
}
|
||||||
w.Flush()
|
w.Flush()
|
||||||
fmt.Printf("\n%d/%d C++ apps conform.\n", len(audits)-bad, len(audits))
|
fmt.Printf("\n%d/%d C++ apps conform.\n", len(audits)-bad, len(audits))
|
||||||
|
|
||||||
|
// BeginTable migration section.
|
||||||
|
if len(tableAudits) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hasMigrationNotes := false
|
||||||
|
for _, t := range tableAudits {
|
||||||
|
if t.Status != "clean" {
|
||||||
|
hasMigrationNotes = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasMigrationNotes {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("\n--- BeginTable migration (issue 0081) ---")
|
||||||
|
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||||
|
fmt.Fprintln(tw, "STATUS\tAPP\tTABLES\tMESSAGE")
|
||||||
|
for _, t := range tableAudits {
|
||||||
|
if t.Status == "clean" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintf(tw, "%s\t%s\t%d\t%s\n", strings.ToUpper(t.Status), t.AppID, t.BeginTableCount, t.Message)
|
||||||
|
}
|
||||||
|
tw.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func doctorArtefacts(root string, jsonOut bool) {
|
func doctorArtefacts(root string, jsonOut bool) {
|
||||||
|
|||||||
+10
-4
@@ -1,16 +1,22 @@
|
|||||||
---
|
---
|
||||||
id: 0081
|
id: 0081
|
||||||
title: tables playground — promote a registry + migrar apps C++ (fase 12)
|
title: tables playground — promote a registry + migrar apps C++ (fase 12)
|
||||||
status: partial
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
created: 2026-05-12
|
created: 2026-05-12
|
||||||
updated: 2026-05-13
|
updated: 2026-05-15
|
||||||
notes: |
|
notes: |
|
||||||
0081-A DONE: 20 types extraidos a cpp/functions/core/data_table_types.h con .md por type
|
0081-A DONE: 20 types extraidos a cpp/functions/core/data_table_types.h con .md por type
|
||||||
(17 core + 3 viz). Playground includes via "core/data_table_types.h", no duplicacion.
|
(17 core + 3 viz). Playground includes via "core/data_table_types.h", no duplicacion.
|
||||||
603 tests pass, e2e linux+windows OK.
|
603 tests pass, e2e linux+windows OK.
|
||||||
0081-B..L PENDING: extraer functions (compute_stage, tql_emit/apply, lua_engine, tql_to_sql,
|
0081-B..L DONE (2026-05-15): 10 funciones registry (8 core + 2 viz), 1 lib fn_table_viz,
|
||||||
join_tables, viz_render, data_table) + fn_table_viz lib + migrar 5 apps.
|
3 apps migradas (chart_demo no aplica, graph_explorer parcial 1/9, registry_dashboard parcial 8/12),
|
||||||
|
fn doctor cpp-apps check anadido (BeginTable inline detection: CANDIDATE/MIXED),
|
||||||
|
docs/TQL.md actualizado con joins, views, main_source, 24 viz tokens, color_rules,
|
||||||
|
derived columns, fn.* sandbox completo (20 builtins), funciones bloqueadas.
|
||||||
|
Deuda: sqlite_api + deploy_server NO migrados (Go apps, requieren TS table system aparte);
|
||||||
|
graph_explorer + registry_dashboard + otras apps C++ marcadas CANDIDATE por fn doctor
|
||||||
|
(migrar en waves futuras con rama TBD dedicada por app).
|
||||||
related_components: [cpp/apps/primitives_gallery/playground/tables, cpp/functions, fn_framework]
|
related_components: [cpp/apps/primitives_gallery/playground/tables, cpp/functions, fn_framework]
|
||||||
---
|
---
|
||||||
|
|
||||||
+289
-20
@@ -58,7 +58,7 @@ Independiente de los datos. La misma query puede renderizarse de N formas con un
|
|||||||
|
|
||||||
### Implicaciones para TQL
|
### Implicaciones para TQL
|
||||||
|
|
||||||
TQL adopta esa separacion: `stages` (data) + `display` + `columns` (viz). Mismo patron, sintaxis Lua.
|
TQL adopta esa separacion: `stages` (data) + `display` + `columns` (viz) + `views` (paneles adicionales). Mismo patron, sintaxis Lua.
|
||||||
|
|
||||||
Cuando un boton futuro "Add visualization" se construya, anade un nuevo `display` + viz settings a una query existente sin tocar `stages`. Asi tendremos M visualizaciones (table, bar, line, scatter) sobre los mismos datos transformados.
|
Cuando un boton futuro "Add visualization" se construya, anade un nuevo `display` + viz settings a una query existente sin tocar `stages`. Asi tendremos M visualizaciones (table, bar, line, scatter) sobre los mismos datos transformados.
|
||||||
|
|
||||||
@@ -103,6 +103,10 @@ input_cells (raw dataset)
|
|||||||
return {
|
return {
|
||||||
version = 1,
|
version = 1,
|
||||||
display = "table",
|
display = "table",
|
||||||
|
main_source = "functions", -- opcional: nombre de la fuente principal
|
||||||
|
|
||||||
|
-- JOINS: unir tablas adicionales antes de stage 0
|
||||||
|
joins = { ... }, -- opcional
|
||||||
|
|
||||||
-- DATA: pipeline de transformacion
|
-- DATA: pipeline de transformacion
|
||||||
stages = {
|
stages = {
|
||||||
@@ -129,9 +133,14 @@ return {
|
|||||||
color_rules = { {equals = "0.0", color = "#e08060"} }},
|
color_rules = { {equals = "0.0", color = "#e08060"} }},
|
||||||
{name = "internal", type = "string", visible = false, order = 3},
|
{name = "internal", type = "string", visible = false, order = 3},
|
||||||
},
|
},
|
||||||
visualization_settings = {
|
|
||||||
-- Futuro: opciones especificas del display (chart axes, paleta, stack, etc.)
|
-- VIEWS: paneles de visualizacion (index 1 = principal, resto = extras)
|
||||||
|
views = {
|
||||||
|
{display = "table"},
|
||||||
|
{display = "bar", x_col = "lang", y_cols = {"count"}},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visualization_settings = {},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -139,6 +148,57 @@ return {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## `main_source`
|
||||||
|
|
||||||
|
Campo de cadena opcional. Identifica el nombre de la tabla/fuente principal del dataset. Usado por `tql_to_sql` para generar el `FROM "main_source"` correcto en el SQL emitido. Si esta vacio, el motor usa la tabla por defecto del contexto.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
main_source = "functions"
|
||||||
|
```
|
||||||
|
|
||||||
|
En el SQL emitido: `FROM "functions"`. Util cuando la app expone multiples tablas y el agente necesita especificar explicitamente cual es la base del query.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `joins`
|
||||||
|
|
||||||
|
Lista de joins que se aplican antes de stage 0. Los campos de las tablas unidas se añaden como columnas adicionales accesibles en todos los stages.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
joins = {
|
||||||
|
{
|
||||||
|
alias = "t", -- prefijo para sus columnas ("t.field")
|
||||||
|
source = "types", -- nombre de la tabla a unir
|
||||||
|
strategy = "left", -- "left" | "inner" | "right" | "full"
|
||||||
|
on = {{"id", "t.id"}}, -- pares {col_izq, col_der}
|
||||||
|
fields = {"t.algebraic", "t.description"}, -- cols a incluir (opcional)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
alias = "u",
|
||||||
|
source = "unit_tests",
|
||||||
|
strategy = "inner",
|
||||||
|
on = {{"id", "u.function_id"}, {"lang", "u.lang"}}, -- multi-key
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Estrategias:**
|
||||||
|
|
||||||
|
| Token | Semantica SQL |
|
||||||
|
|---|---|
|
||||||
|
| `"left"` | `LEFT OUTER JOIN` — todas las filas de la izq, nulls donde no hay match |
|
||||||
|
| `"inner"` | `INNER JOIN` — solo filas con match en ambas tablas |
|
||||||
|
| `"right"` | `RIGHT OUTER JOIN` — todas las filas de la der |
|
||||||
|
| `"full"` | `FULL OUTER JOIN` — todas las filas de ambas tablas |
|
||||||
|
|
||||||
|
Default si `strategy` se omite: `"left"`.
|
||||||
|
|
||||||
|
**Campos tras el join:** accesibles como `"alias.field"` (ej. `"t.algebraic"`) en filters, breakouts, aggregations y expressions. Si `fields` se omite, se incluyen todas las columnas de la tabla unida con prefijo alias.
|
||||||
|
|
||||||
|
**Join multi-key:** `on` es lista de pares; se traduce a `ON l.k1 = r.k1 AND l.k2 = r.k2`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## `filter`
|
## `filter`
|
||||||
|
|
||||||
Lista de predicados. Multiples filters se combinan con AND implicito.
|
Lista de predicados. Multiples filters se combinan con AND implicito.
|
||||||
@@ -191,6 +251,15 @@ breakout = { "lang", "domain" }
|
|||||||
|
|
||||||
Cada combinacion unica de valores `(lang, domain)` produce una fila en el output. Si `breakout` esta vacio pero hay `aggregation`, todo el dataset se reduce a UNA sola fila.
|
Cada combinacion unica de valores `(lang, domain)` produce una fila en el output. Si `breakout` esta vacio pero hay `aggregation`, todo el dataset se reduce a UNA sola fila.
|
||||||
|
|
||||||
|
**Breakout con granularidad de fecha** — sufijo `:granularity` en el nombre de la col:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
breakout = { "created_at:month", "lang" }
|
||||||
|
-- equivale a GROUP BY date_trunc('month', created_at), lang
|
||||||
|
```
|
||||||
|
|
||||||
|
Granularidades disponibles: `year`, `month`, `week`, `day`, `hour`.
|
||||||
|
|
||||||
**Disponible solo en stages >= 1.**
|
**Disponible solo en stages >= 1.**
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -274,13 +343,97 @@ columns = {
|
|||||||
|
|
||||||
**Cols que no aparecen en `columns`**: mantienen su estado UI actual (visible, posicion natural).
|
**Cols que no aparecen en `columns`**: mantienen su estado UI actual (visible, posicion natural).
|
||||||
|
|
||||||
|
### `color_rules`
|
||||||
|
|
||||||
|
Reglas de color condicional por valor exacto. Se aplican al renderizar cada celda de la columna: si el valor de la celda es igual a `equals`, la celda se colorea con `color`.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
color_rules = {
|
||||||
|
{equals = "go", color = "#86b56b"}, -- verde para Go
|
||||||
|
{equals = "py", color = "#6b8eb5"}, -- azul para Python
|
||||||
|
{equals = "bash", color = "#b58f6b"}, -- naranja para Bash
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Solo soporta igualdad exacta (string match). Para rangos numericos, usa una expression que produzca una etiqueta ("high"/"low") y aplica color_rules sobre esa columna derivada.
|
||||||
|
- Multiples reglas se evaluan en orden; la primera que hace match gana.
|
||||||
|
- Si ningun match: color por defecto del tema.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## `display`
|
## `display`
|
||||||
|
|
||||||
Tipo de visualizacion. v1 solo `"table"`. Futuro: `"bar"`, `"line"`, `"scatter"`, `"pie"`, `"scalar"`, `"area"`, `"pivot"`. Default: `"table"`.
|
Tipo de visualizacion del panel principal. Default: `"table"`.
|
||||||
|
|
||||||
|
**Tokens validos (extraidos de `tql_helpers.cpp`):**
|
||||||
|
|
||||||
|
| Token | Tipo de chart |
|
||||||
|
|---|---|
|
||||||
|
| `"table"` | Tabla de datos (default) |
|
||||||
|
| `"bar"` | Barras horizontales |
|
||||||
|
| `"column"` | Barras verticales |
|
||||||
|
| `"grouped_bar"` | Barras agrupadas por categoria |
|
||||||
|
| `"stacked_bar"` | Barras apiladas |
|
||||||
|
| `"line"` | Lineas |
|
||||||
|
| `"area"` | Area rellena |
|
||||||
|
| `"stairs"` | Escalera (step function) |
|
||||||
|
| `"scatter"` | Dispersion XY |
|
||||||
|
| `"bubble"` | Dispersion XY con tamano variable |
|
||||||
|
| `"histogram"` | Histograma 1D |
|
||||||
|
| `"hist2d"` | Histograma 2D |
|
||||||
|
| `"heatmap"` | Mapa de calor |
|
||||||
|
| `"boxplot"` | Caja y bigotes |
|
||||||
|
| `"stem"` | Stem plot |
|
||||||
|
| `"errorbars"` | Barras de error |
|
||||||
|
| `"pie"` | Sectores (pie chart) |
|
||||||
|
| `"donut"` | Donut |
|
||||||
|
| `"funnel"` | Embudo |
|
||||||
|
| `"waterfall"` | Cascada |
|
||||||
|
| `"kpi"` | Metrica KPI (numero grande) |
|
||||||
|
| `"kpi_grid"` | Grid de KPIs |
|
||||||
|
| `"candlestick"` | Velas (OHLC) |
|
||||||
|
| `"radar"` | Radar / spider |
|
||||||
|
|
||||||
|
Token invalido: `tql_apply` genera warning `"unknown display"` y cae a `"table"`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `views`
|
||||||
|
|
||||||
|
Array de paneles de visualizacion. El indice 1 es el panel principal (equivale al `display` + `viz_config` del State); el resto son paneles extra que se muestran junto a la tabla.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
views = {
|
||||||
|
-- Panel 0 (principal)
|
||||||
|
{display = "bar", x_col = "lang", y_cols = {"count"}, color = "#86b56b"},
|
||||||
|
-- Panel 1 (extra)
|
||||||
|
{display = "pie", cat_col = "lang", y_cols = {"sum_size_kb"}, show_legend = true},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Campos por panel:**
|
||||||
|
|
||||||
|
| Campo | Tipo | Para que |
|
||||||
|
|---|---|---|
|
||||||
|
| `display` | string | Token de tipo de chart (ver tabla `display`) |
|
||||||
|
| `x_col` | string | Columna para eje X (bar, column, line, area, scatter, bubble, etc.) |
|
||||||
|
| `y_cols` | `{string,...}` | Columnas para eje Y. Multiple = multiple series |
|
||||||
|
| `cat_col` | string | Columna de categorias (pie, donut, funnel, radar) |
|
||||||
|
| `size_col` | string | Columna para tamano del burbuja (bubble) |
|
||||||
|
| `color` | string | Color primario `"#rrggbb"`. Sirve para series unicas o acento |
|
||||||
|
| `hist_bins` | int | Numero de bins para histogram / hist2d |
|
||||||
|
| `pie_radius` | float | Radio del donut interior (donut, 0.0 = pie solido) |
|
||||||
|
| `show_legend` | bool | Mostrar leyenda. Default `true` |
|
||||||
|
| `show_markers` | bool | Puntos en lineas/area. Default `false` |
|
||||||
|
| `locked` | bool | Panel fijo — el usuario no puede cerrarlo ni cambiar tipo |
|
||||||
|
|
||||||
|
Si `views` se omite, el emit lo serializa con un panel minimo que replica `state.display`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## `visualization_settings`
|
## `visualization_settings`
|
||||||
|
|
||||||
Reservado para configuracion especifica por tipo de display. v1 vacio. Futuro:
|
Reservado para configuracion especifica por tipo de display. v1 siempre vacio (`{}`). Emitido por `tql_emit` para mantener el round-trip completo. Futuro:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
visualization_settings = {
|
visualization_settings = {
|
||||||
@@ -293,6 +446,8 @@ visualization_settings = {
|
|||||||
|
|
||||||
Sintaxis Metabase: las keys con `.` van entre brackets `[]`.
|
Sintaxis Metabase: las keys con `.` van entre brackets `[]`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## `sort`
|
## `sort`
|
||||||
|
|
||||||
Lista de clauses. Multi-sort por orden de aparicion (primera = primaria).
|
Lista de clauses. Multi-sort por orden de aparicion (primera = primaria).
|
||||||
@@ -318,6 +473,8 @@ Pregunta: "Para las funciones puras con cobertura >= 80%, agrupa por lenguaje y
|
|||||||
|
|
||||||
```lua
|
```lua
|
||||||
return {
|
return {
|
||||||
|
version = 1,
|
||||||
|
display = "table",
|
||||||
stages = {
|
stages = {
|
||||||
-- Stage 0: Raw + filter
|
-- Stage 0: Raw + filter
|
||||||
{
|
{
|
||||||
@@ -350,6 +507,44 @@ return {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Ejemplo con join + views
|
||||||
|
|
||||||
|
```lua
|
||||||
|
return {
|
||||||
|
version = 1,
|
||||||
|
display = "bar",
|
||||||
|
main_source = "functions",
|
||||||
|
joins = {
|
||||||
|
{
|
||||||
|
alias = "u",
|
||||||
|
source = "unit_tests",
|
||||||
|
strategy = "left",
|
||||||
|
on = {{"id", "u.function_id"}},
|
||||||
|
fields = {"u.name"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stages = {
|
||||||
|
{ filter = {{"=", "lang", "go"}} },
|
||||||
|
{
|
||||||
|
breakout = {"domain"},
|
||||||
|
aggregation = {{"count"}, {"distinct", "id"}},
|
||||||
|
sort = {{"desc", "count"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
columns = {
|
||||||
|
{name = "domain", type = "string", visible = true, order = 1},
|
||||||
|
{name = "count", type = "int", visible = true, order = 2},
|
||||||
|
},
|
||||||
|
views = {
|
||||||
|
{display = "bar", x_col = "domain", y_cols = {"count"}, show_legend = false},
|
||||||
|
{display = "donut", cat_col = "domain", y_cols = {"count"}, show_legend = true},
|
||||||
|
},
|
||||||
|
visualization_settings = {},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Drill-down (semantica)
|
## Drill-down (semantica)
|
||||||
|
|
||||||
Si el usuario interactua con una celda agrupada del stage N, hace **drill-down**:
|
Si el usuario interactua con una celda agrupada del stage N, hace **drill-down**:
|
||||||
@@ -387,8 +582,7 @@ Las strings dentro de `expressions` siguen el mini-DSL Lua de columnas custom. R
|
|||||||
- Type-aware: cell de col Int/Float llega como number; Bool como boolean; resto como string. Vacia = nil.
|
- Type-aware: cell de col Int/Float llega como number; Bool como boolean; resto como string. Vacia = nil.
|
||||||
- UTF-8 ok en nombres `[año]`.
|
- UTF-8 ok en nombres `[año]`.
|
||||||
- Comentarios `--` y `--[[ ]]` respetados.
|
- Comentarios `--` y `--[[ ]]` respetados.
|
||||||
- Builtins disponibles via `fn.*`: `upper, lower, length, substring, contains, starts_with, ends_with, replace, trim, concat, to_number, to_string, to_bool, is_null, is_empty, coalesce, parse_date, year, month, day`.
|
- Nombres de cols con espacios y puntos soportados en brackets: `[col con espacio]`, `[alias.field]`.
|
||||||
- Sandbox: sin `io`, `require`, `dofile`, `loadfile`, `load`, `package`, `debug`. `os` recortado a `date/time/difftime/clock`.
|
|
||||||
|
|
||||||
Ejemplos:
|
Ejemplos:
|
||||||
|
|
||||||
@@ -397,21 +591,73 @@ Ejemplos:
|
|||||||
fn.concat([lang], ":", [domain]) -- string compose
|
fn.concat([lang], ":", [domain]) -- string compose
|
||||||
if [coverage_pct] >= 90 then "well" else "low" end
|
if [coverage_pct] >= 90 then "well" else "low" end
|
||||||
fn.year([updated_at]) -- date helper
|
fn.year([updated_at]) -- date helper
|
||||||
|
fn.coalesce([error_type], "none") -- null handling
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Funciones Lua disponibles (`fn.*`)
|
||||||
|
|
||||||
|
El sandbox expone estas funciones via la tabla global `fn`. Registradas en `lua_engine.cpp::register_builtins`:
|
||||||
|
|
||||||
|
| Funcion | Firma | Que hace |
|
||||||
|
|---|---|---|
|
||||||
|
| `fn.upper(s)` | string -> string | Convierte a mayusculas (ASCII) |
|
||||||
|
| `fn.lower(s)` | string -> string | Convierte a minusculas (ASCII) |
|
||||||
|
| `fn.length(s)` | string -> int | Longitud en bytes (`strlen`); nil -> 0 |
|
||||||
|
| `fn.substring(s, start [, len])` | string, int[, int] -> string | Subcadena 1-based; len omitido = hasta el final |
|
||||||
|
| `fn.contains(haystack, needle)` | string, string -> bool | True si needle aparece en haystack |
|
||||||
|
| `fn.starts_with(s, prefix)` | string, string -> bool | True si s empieza por prefix |
|
||||||
|
| `fn.ends_with(s, suffix)` | string, string -> bool | True si s termina por suffix |
|
||||||
|
| `fn.replace(s, find, repl)` | string, string, string -> string | Reemplaza todas las ocurrencias de find por repl |
|
||||||
|
| `fn.trim(s)` | string -> string | Elimina espacios/tabs/newlines del inicio y fin |
|
||||||
|
| `fn.concat(...)` | vararg -> string | Concatena N argumentos como string |
|
||||||
|
| `fn.to_number(s)` | string -> number\|nil | Parsea a numero; nil si no parseable |
|
||||||
|
| `fn.to_string(x)` | any -> string | Convierte a string (usa `luaL_tolstring`) |
|
||||||
|
| `fn.to_bool(x)` | any -> bool | True si `"true"` o `"1"` |
|
||||||
|
| `fn.is_null(x)` | any -> bool | True si x es nil |
|
||||||
|
| `fn.is_empty(x)` | any -> bool | True si x es nil o string vacia |
|
||||||
|
| `fn.coalesce(...)` | vararg -> any | Devuelve el primer argumento no-nil |
|
||||||
|
| `fn.parse_date(s)` | string -> table\|nil | Parsea `"YYYY-MM-DD"` -> `{year, month, day}` |
|
||||||
|
| `fn.year(s)` | string -> int\|nil | Extrae el año de `"YYYY-..."` |
|
||||||
|
| `fn.month(s)` | string -> int\|nil | Extrae el mes de `"YYYY-MM-..."` |
|
||||||
|
| `fn.day(s)` | string -> int\|nil | Extrae el dia de `"YYYY-MM-DD"` |
|
||||||
|
|
||||||
|
Ademas, las librerias Lua estandar `string`, `table`, `math`, `os` (recortado) estan disponibles.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sandbox — funciones bloqueadas
|
||||||
|
|
||||||
|
El engine aplica el sandbox via `lua_engine.cpp::apply_sandbox`. Globals eliminados:
|
||||||
|
|
||||||
|
| Global | Por que bloqueado |
|
||||||
|
|---|---|
|
||||||
|
| `io` | I/O de archivos y stdin/stdout |
|
||||||
|
| `require` | Carga de modulos externos |
|
||||||
|
| `loadfile` | Ejecucion de archivos Lua arbitrarios |
|
||||||
|
| `dofile` | Idem |
|
||||||
|
| `load` | Compilacion y ejecucion de strings arbitrarias |
|
||||||
|
| `package` | Sistema de paquetes Lua |
|
||||||
|
| `debug` | Introspection de call stack / upvalues |
|
||||||
|
|
||||||
|
`os` se sustituye por una version recortada que solo expone: `os.date`, `os.time`, `os.difftime`, `os.clock`. El resto de `os` (ejecutar comandos, salir, setenv, etc.) se elimina.
|
||||||
|
|
||||||
|
Las formulas de expresiones se compilan con `luaL_loadbufferx(..., "t")` — el flag `"t"` rechaza bytecode precompilado (solo acepta texto source).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Restricciones v1
|
## Restricciones v1
|
||||||
|
|
||||||
| No soportado | Workaround |
|
| No soportado | Workaround |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Joins entre tablas | Pre-procesar fuera del registry. |
|
|
||||||
| Subqueries SQL | Usar stages encadenados (modelo equivalente). |
|
|
||||||
| `HAVING` post-aggregation | Stage siguiente con `filter` sobre cols agregadas. |
|
| `HAVING` post-aggregation | Stage siguiente con `filter` sobre cols agregadas. |
|
||||||
| `LIMIT` | TBD — añadir como `limit = N` en stage v2. |
|
| `LIMIT` | TBD — añadir como `limit = N` en stage v2. |
|
||||||
| Window functions | TBD. |
|
| Window functions | TBD. |
|
||||||
| Custom aggregation Lua | TBD — `{"lua", "col", "<body>"}`. |
|
| Custom aggregation Lua | TBD — `{"lua", "col", "<body>"}`. |
|
||||||
| Alias custom en aggregation v1 | Crear expression post-grupo. |
|
| Alias custom en aggregation v1 | Crear expression post-grupo. |
|
||||||
|
| color_rules con rangos numericos | Usar expression que emita etiquetas; aplicar color_rules sobre la etiqueta. |
|
||||||
|
| Multiples fuentes sin join | Declarar cada fuente adicional en `joins`. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -423,11 +669,21 @@ Cuando expongas TQL a un LLM, dale este preambulo:
|
|||||||
You output TQL — a Lua table that describes a table transformation. Format:
|
You output TQL — a Lua table that describes a table transformation. Format:
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
version = 1,
|
||||||
|
display = "table", -- table|bar|column|grouped_bar|stacked_bar|line|area|stairs|scatter|
|
||||||
|
-- bubble|histogram|hist2d|heatmap|boxplot|stem|errorbars|
|
||||||
|
-- pie|donut|funnel|waterfall|kpi|kpi_grid|candlestick|radar
|
||||||
|
main_source = "...", -- optional: name of main table/source
|
||||||
|
joins = { ... }, -- optional: join additional tables
|
||||||
stages = {
|
stages = {
|
||||||
{ filter = {...}, expressions = {...}, sort = {...} }, -- Stage 0 (Raw)
|
{ filter = {...}, expressions = {...}, sort = {...} }, -- Stage 0 (Raw)
|
||||||
{ filter = {...}, breakout = {...}, aggregation = {...}, sort = {...} }, -- Stage 1+
|
{ filter = {...}, breakout = {...}, aggregation = {...}, sort = {...} }, -- Stage 1+
|
||||||
...
|
...
|
||||||
}
|
},
|
||||||
|
views = {
|
||||||
|
{display="...", x_col="...", y_cols={...}, cat_col="...", color="...", ...}, -- panel 0 = main
|
||||||
|
... -- extra panels
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
@@ -437,6 +693,9 @@ Rules:
|
|||||||
Available fns: count, sum, avg, min, max, distinct, stddev, median, p25, p75, p90, p99, percentile.
|
Available fns: count, sum, avg, min, max, distinct, stddev, median, p25, p75, p90, p99, percentile.
|
||||||
- Sort: {{"desc", "col"}, ...}. Multi-sort por orden de la lista.
|
- Sort: {{"desc", "col"}, ...}. Multi-sort por orden de la lista.
|
||||||
- Expressions value es una expresion Lua. Acceso a cols via [col_name].
|
- Expressions value es una expresion Lua. Acceso a cols via [col_name].
|
||||||
|
- Joins: alias + source + strategy (left/inner/right/full) + on pairs + optional fields list.
|
||||||
|
- Views: array de paneles, index 1 = principal. display token from the list above.
|
||||||
|
- color_rules: [{equals="val", color="#rrggbb"}, ...] dentro de cada entry de columns.
|
||||||
|
|
||||||
The available columns of the current input table are: <inject runtime>.
|
The available columns of the current input table are: <inject runtime>.
|
||||||
The available column types: <inject runtime>.
|
The available column types: <inject runtime>.
|
||||||
@@ -483,19 +742,25 @@ StageOutput compute_stage(const char* const* in_cells, int in_rows, int in_cols,
|
|||||||
| Feature | Status |
|
| Feature | Status |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `Stage` + `Aggregation` types | done |
|
| `Stage` + `Aggregation` types | done |
|
||||||
| `compute_stage` (filter + group + agg + sort) | done (Phase 1) |
|
| `compute_stage` (filter + group + agg + sort) | done |
|
||||||
| Todas las aggregations (count..percentile) | done |
|
| Todas las aggregations (count..percentile) | done |
|
||||||
| `aggregation_alias` / `aggregation_type` | done |
|
| `aggregation_alias` / `aggregation_type` | done |
|
||||||
| Multi-sort por stage | done |
|
| Multi-sort por stage | done |
|
||||||
| Tests E2E logica | done (37 checks) |
|
| Tests E2E logica | done (129 checks en tql_emit_test + tql_apply_test) |
|
||||||
| `tql_emit` / `tql_apply` (Lua round-trip) | Phase 2 (pendiente) |
|
| `tql_emit` / `tql_apply` (Lua round-trip) | done |
|
||||||
| State refactor a `vector<Stage>` | Phase 3 (pendiente) |
|
| `views` (paneles de visualizacion) | done |
|
||||||
| UI breadcrumb stages + chips por stage | Phase 3 (pendiente) |
|
| `main_source` | done |
|
||||||
| Drill-down interactivo | Phase 3 (pendiente) |
|
| `joins` (left/inner/right/full, multi-key, fields) | done |
|
||||||
| Show TQL / Apply TQL modals | Phase 2 |
|
| `color_rules` por columna | done |
|
||||||
| Multi-sort drag-reorder | Phase 4 |
|
| `breakout` con granularidad de fecha | done |
|
||||||
|
| Lua sandbox (`fn.*` builtins, sin io/require/load) | done |
|
||||||
Ver `cpp/apps/primitives_gallery/playground/tables/` para la implementacion del playground.
|
| 24 tipos de viz (table, bar, column, pie, donut...) | done |
|
||||||
|
| `tql_to_sql` (SQL DuckDB emit) | done (issue 0080) |
|
||||||
|
| State refactor a `vector<Stage>` | done |
|
||||||
|
| UI breadcrumb stages + chips por stage | done |
|
||||||
|
| Drill-down interactivo | done |
|
||||||
|
| Show TQL / Apply TQL modals | done |
|
||||||
|
| Multi-sort drag-reorder | done |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -580,3 +845,7 @@ SQL transpile error en derived col 'fullname':
|
|||||||
- **Agente flow:** TQL default. SQL solo si app linko DuckDB. UI Ask AI muestra toggle SQL solo cuando disponible.
|
- **Agente flow:** TQL default. SQL solo si app linko DuckDB. UI Ask AI muestra toggle SQL solo cuando disponible.
|
||||||
|
|
||||||
Ver issue 0080 + `tql_to_sql.{h,cpp}` para implementacion.
|
Ver issue 0080 + `tql_to_sql.{h,cpp}` para implementacion.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Generado a partir de los tests roundtrip en `cpp/functions/core/tql_emit_test.cpp` y `cpp/functions/core/tql_apply_test.cpp` — 129 checks (41 emit + 88 apply) en verde garantizan compatibilidad del round-trip State <-> Lua.*
|
||||||
|
|||||||
@@ -157,3 +157,120 @@ func cppHasAutoDockspaceFalse(src string) bool {
|
|||||||
return strings.Contains(src, "auto_dockspace = false") ||
|
return strings.Contains(src, "auto_dockspace = false") ||
|
||||||
strings.Contains(src, "auto_dockspace=false")
|
strings.Contains(src, "auto_dockspace=false")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CppTableMigrationAudit holds the BeginTable migration status for a single C++ app.
|
||||||
|
type CppTableMigrationAudit struct {
|
||||||
|
AppID string `json:"app_id"`
|
||||||
|
DirPath string `json:"dir_path"`
|
||||||
|
BeginTableCount int `json:"begin_table_count"`
|
||||||
|
UsesDataTableViz bool `json:"uses_data_table_viz"`
|
||||||
|
Status string `json:"status"` // "clean", "candidate", "mixed"
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditCppTableMigration scans C++ apps (imgui framework) for inline
|
||||||
|
// ImGui::BeginTable calls and checks whether the app already declares
|
||||||
|
// data_table_cpp_viz in its uses_functions.
|
||||||
|
//
|
||||||
|
// Status values:
|
||||||
|
// - "clean" — no ImGui::BeginTable found (fully migrated or never had tables)
|
||||||
|
// - "mixed" — has ImGui::BeginTable AND declares data_table_cpp_viz
|
||||||
|
// (partial migration OK, wave N in progress)
|
||||||
|
// - "candidate" — has ImGui::BeginTable but does NOT declare data_table_cpp_viz
|
||||||
|
// (migration not started; consider data_table_cpp_viz, issue 0081)
|
||||||
|
func AuditCppTableMigration(registryRoot string) ([]CppTableMigrationAudit, error) {
|
||||||
|
dbPath := filepath.Join(registryRoot, "registry.db")
|
||||||
|
dsn := fmt.Sprintf("file:%s?mode=ro&_foreign_keys=on", dbPath)
|
||||||
|
db, err := sql.Open("sqlite3", dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("audit_cpp_table_migration: open db: %w", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
if err := db.Ping(); err != nil {
|
||||||
|
return nil, fmt.Errorf("audit_cpp_table_migration: ping db: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := db.Query(`SELECT id, dir_path, COALESCE(framework,''), COALESCE(uses_functions,'') FROM apps WHERE lang = 'cpp' ORDER BY id`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("audit_cpp_table_migration: query apps: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var results []CppTableMigrationAudit
|
||||||
|
for rows.Next() {
|
||||||
|
var id, dir, framework, usesFunctions string
|
||||||
|
if err := rows.Scan(&id, &dir, &framework, &usesFunctions); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if framework != "imgui" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
absDir := dir
|
||||||
|
if !filepath.IsAbs(absDir) {
|
||||||
|
absDir = filepath.Join(registryRoot, dir)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(absDir); os.IsNotExist(err) {
|
||||||
|
continue // directory_missing already reported by AuditCppApps
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count ImGui::BeginTable occurrences across all .cpp files (excluding tests/).
|
||||||
|
count := 0
|
||||||
|
err := filepath.WalkDir(absDir, func(path string, d os.DirEntry, walkErr error) error {
|
||||||
|
if walkErr != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Skip test directories.
|
||||||
|
if d.IsDir() && (d.Name() == "tests" || d.Name() == "test") {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
if d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(d.Name(), ".cpp") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
src := string(data)
|
||||||
|
for start := 0; ; {
|
||||||
|
idx := strings.Index(src[start:], "ImGui::BeginTable(")
|
||||||
|
if idx < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
start += idx + len("ImGui::BeginTable(")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
usesViz := strings.Contains(usesFunctions, "data_table_cpp_viz")
|
||||||
|
|
||||||
|
audit := CppTableMigrationAudit{
|
||||||
|
AppID: id,
|
||||||
|
DirPath: dir,
|
||||||
|
BeginTableCount: count,
|
||||||
|
UsesDataTableViz: usesViz,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case count == 0:
|
||||||
|
audit.Status = "clean"
|
||||||
|
audit.Message = ""
|
||||||
|
case usesViz:
|
||||||
|
audit.Status = "mixed"
|
||||||
|
audit.Message = fmt.Sprintf("%d ImGui::BeginTable inline (partial migration OK — data_table_cpp_viz already declared)", count)
|
||||||
|
default:
|
||||||
|
audit.Status = "candidate"
|
||||||
|
audit.Message = fmt.Sprintf("candidate to migrate: %d ImGui::BeginTable inline detected, consider data_table_cpp_viz (issue 0081)", count)
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, audit)
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user