feat(0036b): NodeGroups admite kind=Group + loader entities
NodeGroupsWindowState gana un discriminador `kind` (Table | Group) y
un flag `focus_request` (lo consumira 0036c). Por defecto Table, asi
que el flujo historico (DuckDB rows tras expand de un nodo Table) no
cambia.
kind=Group lee directamente operations.db consultando
`entities WHERE group_id = container_id` con columnas fijas
(id, name, type_ref, status, updated_at) ordenadas por updated_at DESC.
Los nuevos loaders viven en node_groups.cpp:
- node_groups_count_for_group -> SELECT count(*) ...
- node_groups_page_for_group -> SELECT id,name,type_ref,status,
updated_at ... LIMIT ? OFFSET ?
Para columnas, opcion (A) del issue: pre-popular meta.columns con la
lista fija al abrir kind=Group, asi el render se mantiene generico.
NodeGroupsRow.values guarda los 5 campos en ese orden y row.id es la
key natural (= entity_id de la fila — al ser ya entidad, no hace falta
promocionarla).
Render en views.cpp ramifica por kind:
- Table: layout original [id_col + columns + promoted] con doble
click -> promote/focus.
- Group: layout [columns fijas] sin promoted. Doble click sobre la
fila ya pone want_focus_entity = id (los flujos posteriores 0036c-e
afinan UX). Right click ofrece "Focus in Inspector".
main.cpp dispatcha por kind al refrescar paginas y, al cerrar via X,
solo llama a node_groups_set_expanded para kind=Table (Group no usa
ese flag).
views_node_groups_windows_sync se hace kind-aware: solo reconcilia
entries kind=Table contra el set de Tables expandidas; no toca las
entries kind=Group (las gestiona views_node_groups_open).
Nueva API publica:
views_node_groups_open(app, container_id, kind, ops_db)
Crea o reusa la entry, setea focus_request=true y para kind=Group
pre-popula meta.columns + intenta leer `name` del Group para el
titulo. Sin caller todavia — la consume 0036c.
Tests:
- tests/test_node_groups_loader.py (6 tests) verifica el contrato
SQL via gx-cli. Nuevo subcomando `gx-cli group page <id>` espejea
el loader C++ exactamente (mismo SQL); tambien expuesto como tool
MCP `group_page` para que Echo pueda inspeccionar Groups.
Resultado:
- WSL: 89 -> 95 passed
- Windows: 78+11 -> 84+11 passed
- Build C++ Windows limpio, sin warnings nuevos.
- Regresion kind=Table: comportamiento identico (mismo render,
mismo loader DuckDB).
Refs: issues/0036b-kind-discriminator-and-group-loader.md
This commit is contained in:
@@ -560,6 +560,38 @@ def cmd_table_page(args) -> None:
|
||||
"offset": args.offset, "limit": args.limit, "rows": rows})
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# group ops (issue 0036b) — espejo Python del loader C++ kind=Group
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
def cmd_group_page(args) -> None:
|
||||
"""Lista entidades hijas de un Group (entities.group_id = ?).
|
||||
|
||||
Espejea exactamente la query del loader C++
|
||||
`node_groups_page_for_group` para que los tests pytest verifiquen
|
||||
el contrato SQL (mismo orden de filas, mismas columnas) sin depender
|
||||
del binario. Util tambien como tool MCP para que el agente Echo
|
||||
inspeccione el contenido de un Group sin abrir la app.
|
||||
"""
|
||||
cn = _connect(_ops_db(), readonly=True)
|
||||
total = cn.execute(
|
||||
"SELECT count(*) FROM entities WHERE group_id = ?",
|
||||
(args.container_id,),
|
||||
).fetchone()[0]
|
||||
limit = max(1, min(int(args.limit), 5000))
|
||||
offset = max(0, int(args.offset))
|
||||
cur = cn.execute(
|
||||
"SELECT id, name, type_ref, status, updated_at "
|
||||
"FROM entities WHERE group_id = ? "
|
||||
"ORDER BY updated_at DESC LIMIT ? OFFSET ?",
|
||||
(args.container_id, limit, offset),
|
||||
)
|
||||
rows = [dict(r) for r in cur.fetchall()]
|
||||
cn.close()
|
||||
_emit({"ok": True, "container": args.container_id, "total": total,
|
||||
"offset": offset, "limit": limit, "rows": rows})
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# enricher ops
|
||||
# ----------------------------------------------------------------------------
|
||||
@@ -841,6 +873,13 @@ MCP_TOOLS = [
|
||||
"description": "Borra la entidad promovida. La fila DuckDB queda intacta.",
|
||||
"inputSchema": {"type": "object", "properties": {
|
||||
"id": {"type": "string"}}, "required": ["id"]}},
|
||||
{"name": "group_page",
|
||||
"description": "Lista entidades hijas de un Group (entities.group_id = container_id). Espejea el loader C++ de NodeGroups kind=Group.",
|
||||
"inputSchema": {"type": "object", "properties": {
|
||||
"container_id": {"type": "string"},
|
||||
"offset": {"type": "integer", "default": 0, "minimum": 0},
|
||||
"limit": {"type": "integer", "default": 200, "minimum": 1, "maximum": 5000}},
|
||||
"required": ["container_id"]}},
|
||||
{"name": "enricher_list",
|
||||
"description": "Lista enrichers cargados. Si se pasa type, filtra por applies_to.",
|
||||
"inputSchema": {"type": "object", "properties": {
|
||||
@@ -902,6 +941,7 @@ MCP_DISPATCH = {
|
||||
"table_page": (cmd_table_page, {"offset": 0, "limit": 50}),
|
||||
"table_promote": (cmd_table_promote, {}),
|
||||
"table_demote": (cmd_table_demote, {}),
|
||||
"group_page": (cmd_group_page, {"offset": 0, "limit": 200}),
|
||||
"enricher_list": (cmd_enricher_list, {"type": None}),
|
||||
"enricher_run": (cmd_enricher_run, {"node": None, "params": None}),
|
||||
"query": (cmd_query, {"limit": 100}),
|
||||
@@ -1093,6 +1133,15 @@ def main() -> None:
|
||||
sp.add_argument("--limit", type=int, default=50)
|
||||
sp.set_defaults(fn=cmd_table_page)
|
||||
|
||||
# group (issue 0036b)
|
||||
g = sub.add_parser("group").add_subparsers(dest="op", required=True)
|
||||
sp = g.add_parser("page",
|
||||
help="Lista entidades hijas de un Group (group_id=?)")
|
||||
sp.add_argument("container_id")
|
||||
sp.add_argument("--offset", type=int, default=0)
|
||||
sp.add_argument("--limit", type=int, default=200)
|
||||
sp.set_defaults(fn=cmd_group_page)
|
||||
|
||||
# enricher
|
||||
e = sub.add_parser("enricher").add_subparsers(dest="op", required=True)
|
||||
sp = e.add_parser("list")
|
||||
|
||||
Reference in New Issue
Block a user