From 24c63b60122ee021b634f10242e91651b2d69cbe Mon Sep 17 00:00:00 2001 From: onenewcode Date: Sun, 12 Jul 2026 10:49:25 +0800 Subject: [PATCH] feat(elasticsearch): render REST JSON responses --- .../common/ElasticsearchJsonResponsePanel.vue | 120 +++++++ .../src/components/common/JsonTree.vue | 317 ++++++++++++++++++ .../src/components/layout/ContentArea.vue | 31 +- .../src/components/redis/RedisJsonTree.vue | 247 -------------- .../src/components/redis/RedisValueViewer.vue | 18 +- .../src/lib/common/__tests__/jsonTree.spec.ts | 46 +++ .../common/__tests__/safeJsonFormat.spec.ts | 12 + apps/desktop/src/lib/common/jsonTree.ts | 111 ++++++ apps/desktop/src/lib/common/safeJsonFormat.ts | 14 +- .../shikiJsonHighlighter.ts} | 14 +- .../elasticsearchJsonResponse.ts | 26 ++ crates/dbx-core/Cargo.toml | 2 +- .../dbx-core/src/db/elasticsearch_driver.rs | 311 +++++++++++++++-- .../elasticsearchJsonResponse.test.ts | 70 ++++ packages/app-tests/queryResultToolbar.test.ts | 9 + 15 files changed, 1045 insertions(+), 303 deletions(-) create mode 100644 apps/desktop/src/components/common/ElasticsearchJsonResponsePanel.vue create mode 100644 apps/desktop/src/components/common/JsonTree.vue delete mode 100644 apps/desktop/src/components/redis/RedisJsonTree.vue create mode 100644 apps/desktop/src/lib/common/__tests__/jsonTree.spec.ts create mode 100644 apps/desktop/src/lib/common/jsonTree.ts rename apps/desktop/src/lib/{redis/redisJsonHighlighter.ts => common/shikiJsonHighlighter.ts} (66%) create mode 100644 apps/desktop/src/lib/elasticsearch/elasticsearchJsonResponse.ts create mode 100644 packages/app-tests/elasticsearchJsonResponse.test.ts diff --git a/apps/desktop/src/components/common/ElasticsearchJsonResponsePanel.vue b/apps/desktop/src/components/common/ElasticsearchJsonResponsePanel.vue new file mode 100644 index 000000000..73e24c199 --- /dev/null +++ b/apps/desktop/src/components/common/ElasticsearchJsonResponsePanel.vue @@ -0,0 +1,120 @@ + + + diff --git a/apps/desktop/src/components/common/JsonTree.vue b/apps/desktop/src/components/common/JsonTree.vue new file mode 100644 index 000000000..718a49057 --- /dev/null +++ b/apps/desktop/src/components/common/JsonTree.vue @@ -0,0 +1,317 @@ + + + + + diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index 32a5fe7f6..42323f233 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -48,6 +48,7 @@ const DocumentBrowser = defineAsyncComponent(() => import("@/components/document const MongoGridFsBrowser = defineAsyncComponent(() => import("@/components/document/MongoGridFsBrowser.vue")); const MongoBucketBrowser = defineAsyncComponent(() => import("@/components/document/MongoBucketBrowser.vue")); const VectorBrowser = defineAsyncComponent(() => import("@/components/vector/VectorBrowser.vue")); +const ElasticsearchJsonResponsePanel = defineAsyncComponent(() => import("@/components/common/ElasticsearchJsonResponsePanel.vue")); const MqAdminConsole = defineAsyncComponent(() => import("@/components/mq/MqAdminConsole.vue")); const NacosAdminConsole = defineAsyncComponent(() => import("@/components/nacos/NacosAdminConsole.vue")); const ObjectBrowser = defineAsyncComponent(() => import("@/components/objects/ObjectBrowser.vue")); @@ -70,6 +71,7 @@ import { dataTabExecutionDatabase } from "@/lib/table/dataTabExecutionDatabase"; import { formatShortcut } from "@/lib/editor/shortcutRegistry"; import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect"; import { chartableColumnIndexes } from "@/lib/dataGrid/chartData"; +import { elasticsearchJsonResponseForResult } from "@/lib/elasticsearch/elasticsearchJsonResponse"; import * as api from "@/lib/backend/api"; import { applyMongoGridChangesToDocument, buildMongoUpdateDocument, formatMongoShellLiteral, type MongoInputValue } from "@/lib/mongo/mongoDocumentValues"; import type { SqlExecutionOverride } from "@/lib/sql/sqlExecutionTarget"; @@ -321,6 +323,7 @@ const resultRuns = computed(() => resultRunItems(props.activeTab)); const activeResultRunItem = computed(() => resultRuns.value.find((run) => run.active)); const activeResultGridCacheKey = computed(() => resultGridCacheKey(props.activeTab)); const activeResultSql = computed(() => resultSqlForGrid(props.activeTab)); +const activeElasticsearchJsonResponse = computed(() => elasticsearchJsonResponseForResult(activeEffectiveDatabaseType.value, activeResultSql.value, props.activeTab.result)); const resultArchiveExporting = ref(false); const canExportResultArchive = computed(() => props.activeTab.mode === "query" && (!!props.activeTab.result || !!props.activeTab.results?.length || !!props.activeTab.resultRuns?.length)); const resultAutoSave = computed(() => props.activeTab.resultAutoSave === true); @@ -339,7 +342,7 @@ const hasTabularResult = computed(() => { }); const canShowResultOutput = computed(() => hasTabularResult.value || props.activeTab.isExecuting); const canShowExplainOutput = computed(() => !!props.activeTab.explainPlan || !!props.activeTab.explainError || !!props.activeTab.explainTableResult || !!props.activeTab.explainTableError || props.activeTab.isExplaining === true); -const showStandaloneResultToolbar = computed(() => props.activeOutputView !== "result" || !props.activeTab.result || !hasTabularResult.value); +const showStandaloneResultToolbar = computed(() => activeElasticsearchJsonResponse.value || props.activeOutputView !== "result" || !props.activeTab.result || !hasTabularResult.value); const standaloneResultToolbarCompact = computed(() => standaloneResultToolbarWidth.value > 0 && standaloneResultToolbarWidth.value < DATA_GRID_COMPACT_TOPBAR_WIDTH); let standaloneResultToolbarResizeObserver: ResizeObserver | undefined; @@ -672,6 +675,12 @@ function refreshData(): boolean { emit("reload"); return true; } + if (activeElasticsearchJsonResponse.value) { + // Match DataGrid's toolbar refresh intent so multi-result runs are + // refreshed as a group instead of replacing them with the active result. + emit("reload", activeResultSql.value, undefined, undefined, undefined, undefined, undefined, "refresh"); + return true; + } if (!dataGridRef.value) return false; void dataGridRef.value.onToolbarRefresh(); return true; @@ -723,7 +732,7 @@ function toggleResultAutoSave() { function handleModRTarget(target: Element): boolean { if (target.closest("[data-query-editor-root]")) return queryEditorRef.value?.openReplace() ?? false; if (target.closest("[data-cell-detail-editor-root]")) return dataGridRef.value?.openCellDetailSearch() ?? false; - if (target.closest("[data-grid-root]")) return refreshData(); + if (target.closest("[data-grid-root], [data-elasticsearch-json-response-root]")) return refreshData(); if (canReloadUnavailableDataTab(props.activeTab)) return refreshData(); return false; } @@ -860,7 +869,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe
- +
- + - +
@@ -1075,8 +1091,9 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe