feat(auto_metabase): push-all + describe/sql + auto-inject de dashcards
- push_all(): pushea todos los YAMLs de un proyecto (cards primero,
dashboards despues), solo CREATE/UPDATE, resiliente a fallos por item
- explore.py: comandos describe (schema de DB) y sql (query ad-hoc con
limite, cap 5MB, bloqueo de escrituras destructivas)
- payload.py: auto-inyecta id:-N, visualization_settings:{} y
parameter_mappings:[] en dashcards nuevas para evitar 500 en push
- test_local: 11 cards + 3 dashboards sobre Sample Database de Metabase
- registry.db regenerado con auto_metabase_py_analytics indexada
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
METABASE_EMAIL=admin@auto-metabase.local
|
||||
METABASE_PASSWORD=AutoMetabase123!
|
||||
@@ -0,0 +1,28 @@
|
||||
_meta:
|
||||
kind: card
|
||||
id: 48
|
||||
slug: clientes_nuevos_por_mes
|
||||
synced_at: '2026-04-13T10:49:59Z'
|
||||
remote_updated_at: '2026-04-13T10:49:59.096661Z'
|
||||
_refs:
|
||||
database: sample_database
|
||||
collection: null
|
||||
payload:
|
||||
description: Altas mensuales en PEOPLE (CREATED_AT)
|
||||
archived: false
|
||||
enable_embedding: false
|
||||
query_type: native
|
||||
name: Clientes nuevos por mes
|
||||
type: question
|
||||
dataset_query:
|
||||
lib/type: mbql/query
|
||||
database: sample_database
|
||||
stages:
|
||||
- lib/type: mbql.stage/native
|
||||
native: "SELECT FORMATDATETIME(CREATED_AT, 'yyyy-MM') AS mes,\n COUNT(*) AS nuevos\n FROM PEOPLE\n GROUP BY mes\n\
|
||||
\ ORDER BY mes"
|
||||
parameter_mappings: []
|
||||
display: line
|
||||
collection_preview: true
|
||||
visualization_settings: {}
|
||||
parameters: []
|
||||
@@ -0,0 +1,31 @@
|
||||
_meta:
|
||||
kind: card
|
||||
id: 49
|
||||
slug: clientes_por_edad
|
||||
synced_at: '2026-04-13T10:49:59Z'
|
||||
remote_updated_at: '2026-04-13T10:49:59.154517Z'
|
||||
_refs:
|
||||
database: sample_database
|
||||
collection: null
|
||||
payload:
|
||||
description: Distribucion calculada desde BIRTH_DATE a fecha actual
|
||||
archived: false
|
||||
enable_embedding: false
|
||||
query_type: native
|
||||
name: Clientes por rango de edad
|
||||
type: question
|
||||
dataset_query:
|
||||
lib/type: mbql/query
|
||||
database: sample_database
|
||||
stages:
|
||||
- lib/type: mbql.stage/native
|
||||
native: "SELECT\n CASE\n WHEN DATEDIFF('year', BIRTH_DATE, CURRENT_DATE) < 25 THEN '1) <25'\n WHEN DATEDIFF('year',\
|
||||
\ BIRTH_DATE, CURRENT_DATE) < 35 THEN '2) 25-34'\n WHEN DATEDIFF('year', BIRTH_DATE, CURRENT_DATE) < 45 THEN '3)\
|
||||
\ 35-44'\n WHEN DATEDIFF('year', BIRTH_DATE, CURRENT_DATE) < 55 THEN '4) 45-54'\n WHEN DATEDIFF('year', BIRTH_DATE,\
|
||||
\ CURRENT_DATE) < 65 THEN '5) 55-64'\n ELSE '6) 65+'\n END AS rango_edad,\n COUNT(*) AS clientes\nFROM PEOPLE\n\
|
||||
WHERE BIRTH_DATE IS NOT NULL\nGROUP BY rango_edad\nORDER BY rango_edad"
|
||||
parameter_mappings: []
|
||||
display: bar
|
||||
collection_preview: true
|
||||
visualization_settings: {}
|
||||
parameters: []
|
||||
@@ -0,0 +1,27 @@
|
||||
_meta:
|
||||
kind: card
|
||||
id: 46
|
||||
slug: clientes_por_estado
|
||||
synced_at: '2026-04-13T10:49:59Z'
|
||||
remote_updated_at: '2026-04-13T10:49:59.215737Z'
|
||||
_refs:
|
||||
database: sample_database
|
||||
collection: null
|
||||
payload:
|
||||
description: Top 10 estados (US) por numero de clientes
|
||||
archived: false
|
||||
enable_embedding: false
|
||||
query_type: native
|
||||
name: clientes_por_estado
|
||||
type: question
|
||||
dataset_query:
|
||||
lib/type: mbql/query
|
||||
database: sample_database
|
||||
stages:
|
||||
- lib/type: mbql.stage/native
|
||||
native: "SELECT STATE, COUNT(*) AS clientes\n FROM PEOPLE\n GROUP BY STATE\n ORDER BY clientes DESC\n LIMIT 10"
|
||||
parameter_mappings: []
|
||||
display: bar
|
||||
collection_preview: true
|
||||
visualization_settings: {}
|
||||
parameters: []
|
||||
@@ -0,0 +1,28 @@
|
||||
_meta:
|
||||
kind: card
|
||||
id: 50
|
||||
slug: clientes_por_source
|
||||
synced_at: '2026-04-13T10:49:59Z'
|
||||
remote_updated_at: '2026-04-13T10:49:59.276556Z'
|
||||
_refs:
|
||||
database: sample_database
|
||||
collection: null
|
||||
payload:
|
||||
description: Distribucion por SOURCE (Google, Twitter, Facebook, Organic, Affiliate)
|
||||
archived: false
|
||||
enable_embedding: false
|
||||
query_type: native
|
||||
name: Clientes por canal
|
||||
type: question
|
||||
dataset_query:
|
||||
lib/type: mbql/query
|
||||
database: sample_database
|
||||
stages:
|
||||
- lib/type: mbql.stage/native
|
||||
native: "SELECT COALESCE(SOURCE, 'unknown') AS canal,\n COUNT(*) AS clientes\n FROM PEOPLE\n GROUP BY canal\n\
|
||||
\ ORDER BY clientes DESC"
|
||||
parameter_mappings: []
|
||||
display: pie
|
||||
collection_preview: true
|
||||
visualization_settings: {}
|
||||
parameters: []
|
||||
@@ -0,0 +1,27 @@
|
||||
_meta:
|
||||
kind: card
|
||||
id: 51
|
||||
slug: clientes_recientes
|
||||
synced_at: '2026-04-13T10:49:59Z'
|
||||
remote_updated_at: '2026-04-13T10:49:59.325878Z'
|
||||
_refs:
|
||||
database: sample_database
|
||||
collection: null
|
||||
payload:
|
||||
description: Tabla de las 20 altas mas recientes en PEOPLE
|
||||
archived: false
|
||||
enable_embedding: false
|
||||
query_type: native
|
||||
name: Ultimos 20 clientes registrados
|
||||
type: question
|
||||
dataset_query:
|
||||
lib/type: mbql/query
|
||||
database: sample_database
|
||||
stages:
|
||||
- lib/type: mbql.stage/native
|
||||
native: "SELECT NAME, EMAIL, CITY, STATE, SOURCE, CREATED_AT\n FROM PEOPLE\n ORDER BY CREATED_AT DESC\n LIMIT 20"
|
||||
parameter_mappings: []
|
||||
display: table
|
||||
collection_preview: true
|
||||
visualization_settings: {}
|
||||
parameters: []
|
||||
@@ -0,0 +1,28 @@
|
||||
_meta:
|
||||
kind: card
|
||||
id: 52
|
||||
slug: clientes_top_ciudades
|
||||
synced_at: '2026-04-13T10:49:59Z'
|
||||
remote_updated_at: '2026-04-13T10:49:59.37711Z'
|
||||
_refs:
|
||||
database: sample_database
|
||||
collection: null
|
||||
payload:
|
||||
description: Ciudades con mas clientes
|
||||
archived: false
|
||||
enable_embedding: false
|
||||
query_type: native
|
||||
name: Top 15 ciudades
|
||||
type: question
|
||||
dataset_query:
|
||||
lib/type: mbql/query
|
||||
database: sample_database
|
||||
stages:
|
||||
- lib/type: mbql.stage/native
|
||||
native: "SELECT CITY, STATE, COUNT(*) AS clientes\n FROM PEOPLE\n GROUP BY CITY, STATE\n ORDER BY clientes DESC\n LIMIT\
|
||||
\ 15"
|
||||
parameter_mappings: []
|
||||
display: bar
|
||||
collection_preview: true
|
||||
visualization_settings: {}
|
||||
parameters: []
|
||||
@@ -0,0 +1,27 @@
|
||||
_meta:
|
||||
kind: card
|
||||
id: 42
|
||||
slug: clientes_total
|
||||
synced_at: '2026-04-13T10:49:59Z'
|
||||
remote_updated_at: '2026-04-13T10:49:59.441467Z'
|
||||
_refs:
|
||||
database: sample_database
|
||||
collection: null
|
||||
payload:
|
||||
description: Numero total de clientes en PEOPLE
|
||||
archived: false
|
||||
enable_embedding: false
|
||||
query_type: native
|
||||
name: clientes_total
|
||||
type: question
|
||||
dataset_query:
|
||||
lib/type: mbql/query
|
||||
database: sample_database
|
||||
stages:
|
||||
- lib/type: mbql.stage/native
|
||||
native: SELECT COUNT(*) AS total FROM PEOPLE
|
||||
parameter_mappings: []
|
||||
display: scalar
|
||||
collection_preview: true
|
||||
visualization_settings: {}
|
||||
parameters: []
|
||||
@@ -0,0 +1,28 @@
|
||||
_meta:
|
||||
kind: card
|
||||
id: 45
|
||||
slug: compras_por_mes
|
||||
synced_at: '2026-04-13T10:49:59Z'
|
||||
remote_updated_at: '2026-04-13T10:49:59.516411Z'
|
||||
_refs:
|
||||
database: sample_database
|
||||
collection: null
|
||||
payload:
|
||||
description: Numero de compras agrupadas por mes
|
||||
archived: false
|
||||
enable_embedding: false
|
||||
query_type: native
|
||||
name: compras_por_mes
|
||||
type: question
|
||||
dataset_query:
|
||||
lib/type: mbql/query
|
||||
database: sample_database
|
||||
stages:
|
||||
- lib/type: mbql.stage/native
|
||||
native: "SELECT FORMATDATETIME(CREATED_AT, 'yyyy-MM') AS mes,\n COUNT(*) AS compras\n FROM ORDERS\n GROUP BY\
|
||||
\ mes\n ORDER BY mes"
|
||||
parameter_mappings: []
|
||||
display: line
|
||||
collection_preview: true
|
||||
visualization_settings: {}
|
||||
parameters: []
|
||||
@@ -0,0 +1,27 @@
|
||||
_meta:
|
||||
kind: card
|
||||
id: 43
|
||||
slug: compras_total
|
||||
synced_at: '2026-04-13T10:49:59Z'
|
||||
remote_updated_at: '2026-04-13T10:49:59.586558Z'
|
||||
_refs:
|
||||
database: sample_database
|
||||
collection: null
|
||||
payload:
|
||||
description: Numero total de compras en ORDERS
|
||||
archived: false
|
||||
enable_embedding: false
|
||||
query_type: native
|
||||
name: compras_total
|
||||
type: question
|
||||
dataset_query:
|
||||
lib/type: mbql/query
|
||||
database: sample_database
|
||||
stages:
|
||||
- lib/type: mbql.stage/native
|
||||
native: SELECT COUNT(*) AS total FROM ORDERS
|
||||
parameter_mappings: []
|
||||
display: scalar
|
||||
collection_preview: true
|
||||
visualization_settings: {}
|
||||
parameters: []
|
||||
@@ -0,0 +1,27 @@
|
||||
_meta:
|
||||
kind: card
|
||||
id: 44
|
||||
slug: ingresos_totales
|
||||
synced_at: '2026-04-13T10:49:59Z'
|
||||
remote_updated_at: '2026-04-13T10:49:59.663393Z'
|
||||
_refs:
|
||||
database: sample_database
|
||||
collection: null
|
||||
payload:
|
||||
description: Suma de TOTAL de todas las compras
|
||||
archived: false
|
||||
enable_embedding: false
|
||||
query_type: native
|
||||
name: ingresos_totales
|
||||
type: question
|
||||
dataset_query:
|
||||
lib/type: mbql/query
|
||||
database: sample_database
|
||||
stages:
|
||||
- lib/type: mbql.stage/native
|
||||
native: SELECT ROUND(SUM(TOTAL), 2) AS ingresos FROM ORDERS
|
||||
parameter_mappings: []
|
||||
display: scalar
|
||||
collection_preview: true
|
||||
visualization_settings: {}
|
||||
parameters: []
|
||||
@@ -0,0 +1,27 @@
|
||||
_meta:
|
||||
kind: card
|
||||
id: 40
|
||||
slug: test_count_users
|
||||
synced_at: '2026-04-13T10:49:59Z'
|
||||
remote_updated_at: '2026-04-13T10:49:59.727051Z'
|
||||
_refs:
|
||||
database: metabase_internal_pg
|
||||
collection: null
|
||||
payload:
|
||||
description: otro cambio externo
|
||||
archived: false
|
||||
enable_embedding: false
|
||||
query_type: native
|
||||
name: test_count_users
|
||||
type: question
|
||||
dataset_query:
|
||||
lib/type: mbql/query
|
||||
database: metabase_internal_pg
|
||||
stages:
|
||||
- lib/type: mbql.stage/native
|
||||
native: SELECT COUNT(*) AS users FROM core_user
|
||||
parameter_mappings: []
|
||||
display: scalar
|
||||
collection_preview: true
|
||||
visualization_settings: {}
|
||||
parameters: []
|
||||
@@ -0,0 +1,26 @@
|
||||
_meta:
|
||||
kind: card
|
||||
id: 41
|
||||
slug: test_users_by_locale
|
||||
synced_at: '2026-04-13T10:49:59Z'
|
||||
remote_updated_at: '2026-04-13T10:49:59.796377Z'
|
||||
_refs:
|
||||
database: metabase_internal_pg
|
||||
collection: null
|
||||
payload:
|
||||
archived: false
|
||||
enable_embedding: false
|
||||
query_type: native
|
||||
name: test_users_by_locale
|
||||
type: question
|
||||
dataset_query:
|
||||
lib/type: mbql/query
|
||||
database: metabase_internal_pg
|
||||
stages:
|
||||
- lib/type: mbql.stage/native
|
||||
native: SELECT COALESCE(locale, 'unknown') AS locale, COUNT(*) AS n FROM core_user GROUP BY locale ORDER BY n DESC
|
||||
parameter_mappings: []
|
||||
display: bar
|
||||
collection_preview: true
|
||||
visualization_settings: {}
|
||||
parameters: []
|
||||
@@ -0,0 +1,29 @@
|
||||
_meta:
|
||||
kind: card
|
||||
id: 47
|
||||
slug: top_clientes
|
||||
synced_at: '2026-04-13T10:49:59Z'
|
||||
remote_updated_at: '2026-04-13T10:49:59.882916Z'
|
||||
_refs:
|
||||
database: sample_database
|
||||
collection: null
|
||||
payload:
|
||||
description: Top 10 clientes por gasto total
|
||||
archived: false
|
||||
enable_embedding: false
|
||||
query_type: native
|
||||
name: top_clientes
|
||||
type: question
|
||||
dataset_query:
|
||||
lib/type: mbql/query
|
||||
database: sample_database
|
||||
stages:
|
||||
- lib/type: mbql.stage/native
|
||||
native: "SELECT P.NAME AS cliente,\n P.EMAIL AS email,\n COUNT(O.ID) AS num_compras,\n ROUND(SUM(O.TOTAL),\
|
||||
\ 2) AS total_gastado\n FROM PEOPLE P\n JOIN ORDERS O ON O.USER_ID = P.ID\n GROUP BY P.NAME, P.EMAIL\n ORDER BY\
|
||||
\ total_gastado DESC\n LIMIT 10"
|
||||
parameter_mappings: []
|
||||
display: table
|
||||
collection_preview: true
|
||||
visualization_settings: {}
|
||||
parameters: []
|
||||
@@ -0,0 +1,15 @@
|
||||
name: test_local
|
||||
description: "Metabase de prueba en Docker local (container auto_metabase_test-metabase)"
|
||||
base_url: http://localhost:3000
|
||||
|
||||
auth:
|
||||
email_env: METABASE_EMAIL
|
||||
password_env: METABASE_PASSWORD
|
||||
|
||||
sync:
|
||||
# IDs de databases a ignorar (1 = Sample Database interno)
|
||||
ignore_databases: [1]
|
||||
# IDs de colecciones a ignorar
|
||||
ignore_collections: []
|
||||
# Si true, archiva en Metabase en vez de eliminar al hacer push
|
||||
prefer_archive: true
|
||||
@@ -0,0 +1,31 @@
|
||||
_meta:
|
||||
kind: dashboard
|
||||
id: 2
|
||||
slug: auto_metabase_test_dashboard
|
||||
synced_at: '2026-04-13T10:50:00Z'
|
||||
remote_updated_at: '2026-04-13T09:43:33.289419Z'
|
||||
dashcards_count: 2
|
||||
tabs_count: 0
|
||||
parameters_count: 0
|
||||
_refs:
|
||||
collection: null
|
||||
payload:
|
||||
description: Dashboard de prueba para auto_metabase
|
||||
archived: false
|
||||
dashcards:
|
||||
- size_x: 6
|
||||
col: 0
|
||||
size_y: 4
|
||||
row: 0
|
||||
card: test_count_users
|
||||
- size_x: 6
|
||||
col: 6
|
||||
size_y: 4
|
||||
row: 0
|
||||
card: test_users_by_locale
|
||||
tabs: []
|
||||
enable_embedding: false
|
||||
name: auto_metabase test dashboard
|
||||
width: fixed
|
||||
parameters: []
|
||||
auto_apply_filters: true
|
||||
@@ -0,0 +1,51 @@
|
||||
_meta:
|
||||
kind: dashboard
|
||||
id: 4
|
||||
slug: compras_y_clientes
|
||||
synced_at: '2026-04-13T10:50:00Z'
|
||||
remote_updated_at: '2026-04-13T10:40:30.171442Z'
|
||||
dashcards_count: 6
|
||||
tabs_count: 0
|
||||
parameters_count: 0
|
||||
_refs:
|
||||
collection: null
|
||||
payload:
|
||||
description: Vista general de compras (ORDERS) y clientes (PEOPLE) del Sample Database
|
||||
archived: false
|
||||
dashcards:
|
||||
- size_x: 12
|
||||
col: 12
|
||||
size_y: 6
|
||||
row: 3
|
||||
card: clientes_por_estado
|
||||
- size_x: 12
|
||||
col: 0
|
||||
size_y: 6
|
||||
row: 3
|
||||
card: compras_por_mes
|
||||
- size_x: 8
|
||||
col: 0
|
||||
size_y: 3
|
||||
row: 0
|
||||
card: clientes_total
|
||||
- size_x: 8
|
||||
col: 8
|
||||
size_y: 3
|
||||
row: 0
|
||||
card: compras_total
|
||||
- size_x: 8
|
||||
col: 16
|
||||
size_y: 3
|
||||
row: 0
|
||||
card: ingresos_totales
|
||||
- size_x: 24
|
||||
col: 0
|
||||
size_y: 7
|
||||
row: 9
|
||||
card: top_clientes
|
||||
tabs: []
|
||||
enable_embedding: false
|
||||
name: Compras y Clientes
|
||||
width: fixed
|
||||
parameters: []
|
||||
auto_apply_filters: true
|
||||
@@ -0,0 +1,36 @@
|
||||
_meta:
|
||||
kind: dashboard
|
||||
id: 5
|
||||
slug: kpis_minimal
|
||||
synced_at: '2026-04-13T10:50:00Z'
|
||||
remote_updated_at: '2026-04-13T10:47:00.301085Z'
|
||||
dashcards_count: 3
|
||||
tabs_count: 0
|
||||
parameters_count: 0
|
||||
_refs:
|
||||
collection: null
|
||||
payload:
|
||||
description: Dashboard test del auto-inject — YAML sin id/viz/param_mappings en dashcards
|
||||
archived: false
|
||||
dashcards:
|
||||
- size_x: 8
|
||||
col: 0
|
||||
size_y: 3
|
||||
row: 0
|
||||
card: clientes_total
|
||||
- size_x: 8
|
||||
col: 8
|
||||
size_y: 3
|
||||
row: 0
|
||||
card: compras_total
|
||||
- size_x: 8
|
||||
col: 16
|
||||
size_y: 3
|
||||
row: 0
|
||||
card: ingresos_totales
|
||||
tabs: []
|
||||
enable_embedding: false
|
||||
name: KPIs Minimal
|
||||
width: fixed
|
||||
parameters: []
|
||||
auto_apply_filters: true
|
||||
@@ -0,0 +1,56 @@
|
||||
_meta:
|
||||
kind: dashboard
|
||||
id: 6
|
||||
slug: panel_clientes
|
||||
synced_at: '2026-04-13T10:50:00Z'
|
||||
remote_updated_at: '2026-04-13T10:50:00.213836Z'
|
||||
dashcards_count: 7
|
||||
tabs_count: 0
|
||||
parameters_count: 0
|
||||
_refs:
|
||||
collection: null
|
||||
payload:
|
||||
description: Vista 360 de los clientes (PEOPLE) del Sample Database — total, altas, canal, geografia, edad y registros recientes.
|
||||
archived: false
|
||||
dashcards:
|
||||
- size_x: 24
|
||||
col: 0
|
||||
size_y: 3
|
||||
row: 0
|
||||
card: clientes_total
|
||||
- size_x: 24
|
||||
col: 0
|
||||
size_y: 6
|
||||
row: 3
|
||||
card: clientes_nuevos_por_mes
|
||||
- size_x: 12
|
||||
col: 0
|
||||
size_y: 6
|
||||
row: 9
|
||||
card: clientes_por_source
|
||||
- size_x: 12
|
||||
col: 12
|
||||
size_y: 6
|
||||
row: 9
|
||||
card: clientes_por_edad
|
||||
- size_x: 12
|
||||
col: 0
|
||||
size_y: 6
|
||||
row: 15
|
||||
card: clientes_por_estado
|
||||
- size_x: 12
|
||||
col: 12
|
||||
size_y: 6
|
||||
row: 15
|
||||
card: clientes_top_ciudades
|
||||
- size_x: 24
|
||||
col: 0
|
||||
size_y: 8
|
||||
row: 21
|
||||
card: clientes_recientes
|
||||
tabs: []
|
||||
enable_embedding: false
|
||||
name: Panel de Clientes
|
||||
width: fixed
|
||||
parameters: []
|
||||
auto_apply_filters: true
|
||||
@@ -0,0 +1,17 @@
|
||||
_meta:
|
||||
kind: database
|
||||
id: 2
|
||||
slug: metabase_internal_pg
|
||||
_refs: {}
|
||||
payload:
|
||||
timezone: GMT
|
||||
auto_run_queries: true
|
||||
name: metabase_internal_pg
|
||||
details:
|
||||
host: auto_metabase_test-postgres
|
||||
port: 5432
|
||||
dbname: metabase
|
||||
user: metabase
|
||||
password: ${METABASE_DB_PASSWORD_METABASE_INTERNAL_PG}
|
||||
ssl: false
|
||||
engine: postgres
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"cards": {
|
||||
"clientes_nuevos_por_mes": 48,
|
||||
"clientes_por_edad": 49,
|
||||
"clientes_por_estado": 46,
|
||||
"clientes_por_source": 50,
|
||||
"clientes_recientes": 51,
|
||||
"clientes_top_ciudades": 52,
|
||||
"clientes_total": 42,
|
||||
"compras_por_mes": 45,
|
||||
"compras_total": 43,
|
||||
"ingresos_totales": 44,
|
||||
"test_count_users": 40,
|
||||
"test_users_by_locale": 41,
|
||||
"top_clientes": 47
|
||||
},
|
||||
"collections": {},
|
||||
"dashboards": {
|
||||
"auto_metabase_test_dashboard": 2,
|
||||
"compras_y_clientes": 4,
|
||||
"kpis_minimal": 5,
|
||||
"panel_clientes": 6
|
||||
},
|
||||
"databases": {
|
||||
"metabase_internal_pg": 2,
|
||||
"sample_database": 1
|
||||
},
|
||||
"documents": {}
|
||||
}
|
||||
Reference in New Issue
Block a user