feat: mejorar visualización de resultados de queries en Metabase Debug
- Agregar tabla estructurada para mostrar resultados de queries - Agregar botón de copiar JSON profesional con feedback visual - Agregar formato automático de números y fechas según tipo de columna - Mantener vista JSON colapsable para ver datos completos - Mejorar UX con tabla responsive y estilos consistentes
This commit is contained in:
@@ -82,9 +82,21 @@
|
|||||||
<div v-if="queryResult" class="mt-4">
|
<div v-if="queryResult" class="mt-4">
|
||||||
<div class="flex flex-wrap justify-between items-center gap-2 mb-2">
|
<div class="flex flex-wrap justify-between items-center gap-2 mb-2">
|
||||||
<h4 class="font-medium text-sm">Resultados</h4>
|
<h4 class="font-medium text-sm">Resultados</h4>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
<UBadge color="green">
|
<UBadge color="green">
|
||||||
{{ queryResult.data?.rows?.length || 0 }} filas en {{ queryResult.running_time || 0 }}ms
|
{{ queryResult.data?.rows?.length || 0 }} filas en {{ queryResult.running_time || 0 }}ms
|
||||||
</UBadge>
|
</UBadge>
|
||||||
|
<UButton
|
||||||
|
v-if="queryResult.data"
|
||||||
|
@click="copyResults"
|
||||||
|
color="gray"
|
||||||
|
variant="soft"
|
||||||
|
size="xs"
|
||||||
|
icon="i-heroicons-clipboard-document"
|
||||||
|
>
|
||||||
|
{{ copied ? 'Copiado!' : 'Copiar JSON' }}
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Empty State -->
|
<!-- Empty State -->
|
||||||
@@ -99,8 +111,43 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Data Display -->
|
<!-- Data Display -->
|
||||||
<div v-else class="overflow-x-auto">
|
<div v-else>
|
||||||
<pre class="p-3 bg-gray-50 dark:bg-gray-900 rounded text-xs whitespace-pre-wrap break-words">{{ JSON.stringify(queryResult.data, null, 2) }}</pre>
|
<!-- Table View -->
|
||||||
|
<div v-if="queryResult.data.cols && queryResult.data.rows" class="overflow-x-auto mb-4">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
|
<thead class="bg-gray-50 dark:bg-gray-800">
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
v-for="col in queryResult.data.cols"
|
||||||
|
:key="col.name"
|
||||||
|
class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"
|
||||||
|
>
|
||||||
|
{{ col.display_name || col.name }}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
|
<tr v-for="(row, rowIndex) in queryResult.data.rows" :key="rowIndex">
|
||||||
|
<td
|
||||||
|
v-for="(cell, cellIndex) in row"
|
||||||
|
:key="cellIndex"
|
||||||
|
class="px-4 py-3 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100"
|
||||||
|
>
|
||||||
|
{{ formatCell(cell, queryResult.data.cols[cellIndex]) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JSON View (collapsible) -->
|
||||||
|
<details class="group">
|
||||||
|
<summary class="cursor-pointer text-sm font-medium text-primary flex items-center gap-2">
|
||||||
|
<span>Ver JSON completo</span>
|
||||||
|
<UIcon name="i-heroicons-chevron-down-20-solid" class="w-4 h-4 transition-transform group-open:rotate-180" />
|
||||||
|
</summary>
|
||||||
|
<pre class="mt-2 p-3 bg-gray-50 dark:bg-gray-900 rounded text-xs whitespace-pre-wrap break-words">{{ JSON.stringify(queryResult.data, null, 2) }}</pre>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -126,6 +173,7 @@ const executing = ref(false)
|
|||||||
const executingCached = ref(false)
|
const executingCached = ref(false)
|
||||||
const queryResult = ref<any>(null)
|
const queryResult = ref<any>(null)
|
||||||
const error = ref<string | null>(null)
|
const error = ref<string | null>(null)
|
||||||
|
const copied = ref(false)
|
||||||
|
|
||||||
const exportItems = [[
|
const exportItems = [[
|
||||||
{
|
{
|
||||||
@@ -203,4 +251,39 @@ function downloadExport(format: 'csv' | 'json' | 'xlsx') {
|
|||||||
const url = `${window.location.origin}/api/metabase/cards/${props.card.id}/query/${format}`
|
const url = `${window.location.origin}/api/metabase/cards/${props.card.id}/query/${format}`
|
||||||
window.open(url, '_blank')
|
window.open(url, '_blank')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function copyResults() {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(JSON.stringify(queryResult.value.data, null, 2))
|
||||||
|
copied.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
copied.value = false
|
||||||
|
}, 2000)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error al copiar:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCell(value: any, col: any) {
|
||||||
|
if (value === null || value === undefined) return '-'
|
||||||
|
|
||||||
|
// Format numbers based on type
|
||||||
|
if (col.base_type === 'type/Float' || col.base_type === 'type/Decimal') {
|
||||||
|
return new Intl.NumberFormat('es-ES', {
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 2
|
||||||
|
}).format(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (col.base_type === 'type/Integer') {
|
||||||
|
return new Intl.NumberFormat('es-ES').format(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format dates
|
||||||
|
if (col.base_type === 'type/Date' || col.base_type === 'type/DateTime') {
|
||||||
|
return new Date(value).toLocaleDateString('es-ES')
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user