This is an automated email from the ASF dual-hosted git repository.

wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git


The following commit(s) were added to refs/heads/main by this push:
     new 3122e6f5 feat(ui): introduce the TanStack Query  and optimize build 
configuration (#845)
3122e6f5 is described below

commit 3122e6f59b85ae6cc41617a2512f76f5e1cdf45e
Author: Fine0830 <[email protected]>
AuthorDate: Wed Nov 12 11:31:20 2025 +0800

    feat(ui): introduce the TanStack Query  and optimize build configuration 
(#845)
---
 dist/LICENSE                                       |   4 +
 .../license--tanstack-match-sorter-utils.txt       |  21 ++
 .../ui-licenses/license--tanstack-query-core.txt   |  21 ++
 .../ui-licenses/license--tanstack-vue-query.txt    |  21 ++
 .../ui-licenses/license-remove-accents.txt         |  22 ++
 ui/LICENSE                                         |   4 +
 ui/package-lock.json                               |  54 ++++
 ui/package.json                                    |   1 +
 ui/src/api/index.js                                | 344 ++++++++++++++-------
 ui/src/components/BydbQL/Index.vue                 |  35 +++
 ui/src/components/CodeMirror/index.vue             |   1 -
 ui/src/components/common/MeasureAndStreamTable.vue |   2 +-
 ui/src/main.js                                     |   2 +
 ui/src/plugins/vue-query.js                        |  40 +++
 ui/vite.config.mjs                                 |  54 +++-
 15 files changed, 518 insertions(+), 108 deletions(-)

diff --git a/dist/LICENSE b/dist/LICENSE
index fadc08b6..51f46e34 100644
--- a/dist/LICENSE
+++ b/dist/LICENSE
@@ -521,6 +521,9 @@ MIT licenses
     @parcel/watcher-linux-x64-glibc 2.5.1 MIT
     @parcel/watcher-linux-x64-musl 2.5.1 MIT
     @popperjs/core 2.11.7 MIT
+    @tanstack/match-sorter-utils 8.19.4 MIT
+    @tanstack/query-core 5.90.7 MIT
+    @tanstack/vue-query 5.90.7 MIT
     @types/lodash 4.17.20 MIT
     @types/lodash-es 4.17.12 MIT
     @types/web-bluetooth 0.0.16 MIT
@@ -572,6 +575,7 @@ MIT licenses
     process-nextick-args 2.0.1 MIT
     readable-stream 2.3.8 MIT
     readdirp 4.1.2 MIT
+    remove-accents 0.5.0 MIT
     safe-buffer 5.1.2 MIT
     sass 1.93.1 MIT
     setimmediate 1.0.5 MIT
diff --git a/dist/licenses/ui-licenses/license--tanstack-match-sorter-utils.txt 
b/dist/licenses/ui-licenses/license--tanstack-match-sorter-utils.txt
new file mode 100644
index 00000000..9fe1442e
--- /dev/null
+++ b/dist/licenses/ui-licenses/license--tanstack-match-sorter-utils.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 Tanner Linsley
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/dist/licenses/ui-licenses/license--tanstack-query-core.txt 
b/dist/licenses/ui-licenses/license--tanstack-query-core.txt
new file mode 100644
index 00000000..1869e21f
--- /dev/null
+++ b/dist/licenses/ui-licenses/license--tanstack-query-core.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021-present Tanner Linsley
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/dist/licenses/ui-licenses/license--tanstack-vue-query.txt 
b/dist/licenses/ui-licenses/license--tanstack-vue-query.txt
new file mode 100644
index 00000000..1869e21f
--- /dev/null
+++ b/dist/licenses/ui-licenses/license--tanstack-vue-query.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021-present Tanner Linsley
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/dist/licenses/ui-licenses/license-remove-accents.txt 
b/dist/licenses/ui-licenses/license-remove-accents.txt
new file mode 100644
index 00000000..6f7b0ec7
--- /dev/null
+++ b/dist/licenses/ui-licenses/license-remove-accents.txt
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Marin Atanasov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/ui/LICENSE b/ui/LICENSE
index 578c0445..c5fc4998 100644
--- a/ui/LICENSE
+++ b/ui/LICENSE
@@ -73,6 +73,9 @@ MIT licenses
     @parcel/watcher-linux-x64-glibc 2.5.1 MIT
     @parcel/watcher-linux-x64-musl 2.5.1 MIT
     @popperjs/core 2.11.7 MIT
+    @tanstack/match-sorter-utils 8.19.4 MIT
+    @tanstack/query-core 5.90.7 MIT
+    @tanstack/vue-query 5.90.7 MIT
     @types/lodash 4.17.20 MIT
     @types/lodash-es 4.17.12 MIT
     @types/web-bluetooth 0.0.16 MIT
@@ -124,6 +127,7 @@ MIT licenses
     process-nextick-args 2.0.1 MIT
     readable-stream 2.3.8 MIT
     readdirp 4.1.2 MIT
+    remove-accents 0.5.0 MIT
     safe-buffer 5.1.2 MIT
     sass 1.93.1 MIT
     setimmediate 1.0.5 MIT
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 1d667065..e9054918 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -9,6 +9,7 @@
       "version": "0.1.0",
       "dependencies": {
         "@element-plus/icons-vue": "^2.3.1",
+        "@tanstack/vue-query": "^5.90.7",
         "codemirror": "^5.65.16",
         "echarts": "^5.5.0",
         "element-plus": "^2.11.3",
@@ -1270,6 +1271,54 @@
         "win32"
       ]
     },
+    "node_modules/@tanstack/match-sorter-utils": {
+      "version": "8.19.4",
+      "resolved": 
"https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz";,
+      "integrity": 
"sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==",
+      "dependencies": {
+        "remove-accents": "0.5.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley";
+      }
+    },
+    "node_modules/@tanstack/query-core": {
+      "version": "5.90.7",
+      "resolved": 
"https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.7.tgz";,
+      "integrity": 
"sha512-6PN65csiuTNfBMXqQUxQhCNdtm1rV+9kC9YwWAIKcaxAauq3Wu7p18j3gQY3YIBJU70jT/wzCCZ2uqto/vQgiQ==",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley";
+      }
+    },
+    "node_modules/@tanstack/vue-query": {
+      "version": "5.90.7",
+      "resolved": 
"https://registry.npmjs.org/@tanstack/vue-query/-/vue-query-5.90.7.tgz";,
+      "integrity": 
"sha512-2h0esebc2qVRVDge3gFArhss5+qn/Wb4abXuaEliSy+/xPWQMrEX7Ny0UKkCy1HcMZZjvfTtNRUfpYrx+vpipw==",
+      "dependencies": {
+        "@tanstack/match-sorter-utils": "^8.19.4",
+        "@tanstack/query-core": "5.90.7",
+        "@vue/devtools-api": "^6.6.3",
+        "vue-demi": "^0.14.10"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley";
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.1.2",
+        "vue": "^2.6.0 || ^3.3.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@types/estree": {
       "version": "1.0.8",
       "resolved": 
"https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz";,
@@ -2312,6 +2361,11 @@
         "url": "https://paulmillr.com/funding/";
       }
     },
+    "node_modules/remove-accents": {
+      "version": "0.5.0",
+      "resolved": 
"https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz";,
+      "integrity": 
"sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="
+    },
     "node_modules/resolve": {
       "version": "1.22.10",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz";,
diff --git a/ui/package.json b/ui/package.json
index ac7afd31..ce6a11e8 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -11,6 +11,7 @@
     "format": "prettier --write  \"src/**/*.{js,tsx,css,scss,vue,html,mjs}\""
   },
   "dependencies": {
+    "@tanstack/vue-query": "^5.90.7",
     "@element-plus/icons-vue": "^2.3.1",
     "codemirror": "^5.65.16",
     "echarts": "^5.5.0",
diff --git a/ui/src/api/index.js b/ui/src/api/index.js
index 7492d060..70ab4c1e 100644
--- a/ui/src/api/index.js
+++ b/ui/src/api/index.js
@@ -17,216 +17,350 @@
  * under the License.
  */
 
+import { queryClient } from '@/plugins/vue-query';
 import { httpQuery } from './base';
 
+/**
+ * Fetches data using TanStack Query with automatic caching.
+ *
+ * Results are cached by queryKey. Subsequent calls with the same queryKey 
return
+ * cached data if available and not stale.
+ *
+ * @param {Array<string|number|boolean>} queryKey - Unique cache key array.
+ *   Example: `['groups']` or `['tableData', type, JSON.stringify(data)]`
+ * @param {Object} request - HTTP request config.
+ * @param {string} request.url - API endpoint URL.
+ * @param {string} request.method - HTTP method ('GET', 'POST', etc.).
+ * @param {Object} [request.json] - Optional JSON payload for request body.
+ * @param {Object} [request.headers={}] - Optional additional headers.
+ * @param {Object} [options={}] - TanStack Query options (e.g., `staleTime`, 
`retry`).
+ * @returns {Promise<any>} Promise resolving to API response or error object.
+ */
+function fetchWithQuery(queryKey, request, options = {}) {
+  return queryClient.fetchQuery({
+    queryKey,
+    queryFn: () => httpQuery(request),
+    ...options,
+  });
+}
+
+async function invalidateQueries(keys = []) {
+  if (!Array.isArray(keys) || keys.length === 0) {
+    return;
+  }
+
+  await Promise.all(
+    keys.map((key) => {
+      if (typeof key === 'function') {
+        return queryClient.invalidateQueries({ predicate: key });
+      }
+      if (key && typeof key === 'object' && key.queryKey) {
+        return queryClient.invalidateQueries(key);
+      }
+      const queryKey = Array.isArray(key) ? key : [key];
+      return queryClient.invalidateQueries({ queryKey });
+    }),
+  );
+}
+
+async function mutateWithInvalidation(request, invalidate = []) {
+  const result = await httpQuery(request);
+  if (!result?.error && invalidate.length) {
+    await invalidateQueries(invalidate);
+  }
+  return result;
+}
+
 export function getGroupList() {
-  return httpQuery({
+  return fetchWithQuery(['groups'], {
     url: '/api/v1/group/schema/lists',
     method: 'GET',
   });
 }
 
 export function getAllTypesOfResourceList(type, name) {
-  return httpQuery({
+  return fetchWithQuery(['groupResources', type, name], {
     url: `/api/v1/${type}/schema/lists/${name}`,
     method: 'GET',
   });
 }
 
 export function getResourceOfAllType(type, group, name) {
-  return httpQuery({
+  return fetchWithQuery(['resource', type, group, name], {
     url: `/api/v1/${type}/schema/${group}/${name}`,
     method: 'GET',
   });
 }
 
 export function getTableList(data, type) {
-  return httpQuery({
-    url: `/api/v1/${type}/data`,
-    json: data,
-    method: 'POST',
-  });
+  const queryKey = ['tableData', type, JSON.stringify(data)];
+  return fetchWithQuery(
+    queryKey,
+    {
+      url: `/api/v1/${type}/data`,
+      json: data,
+      method: 'POST',
+    },
+    { staleTime: 0 },
+  );
 }
 
 export function deleteAllTypesOfResource(type, group, name) {
-  return httpQuery({
-    url: `/api/v1/${type}/schema/${group}/${name}`,
-    method: 'DELETE',
-  });
+  return mutateWithInvalidation(
+    {
+      url: `/api/v1/${type}/schema/${group}/${name}`,
+      method: 'DELETE',
+    },
+    [
+      ['groupResources', type, group],
+      ['resource', type, group, name],
+    ],
+  );
 }
 
 export function deleteGroup(group) {
-  return httpQuery({
-    url: `/api/v1/group/schema/${group}`,
-    method: 'DELETE',
-  });
+  return mutateWithInvalidation(
+    {
+      url: `/api/v1/group/schema/${group}`,
+      method: 'DELETE',
+    },
+    [['groups'], ['groupResources']],
+  );
 }
 
 export function createGroup(data) {
-  return httpQuery({
-    url: `/api/v1/group/schema`,
-    method: 'POST',
-    json: data,
-  });
+  return mutateWithInvalidation(
+    {
+      url: `/api/v1/group/schema`,
+      method: 'POST',
+      json: data,
+    },
+    [['groups'], ['groupResources']],
+  );
 }
 
 export function editGroup(group, data) {
-  return httpQuery({
-    url: `/api/v1/group/schema/${group}`,
-    method: 'PUT',
-    json: data,
-  });
+  return mutateWithInvalidation(
+    {
+      url: `/api/v1/group/schema/${group}`,
+      method: 'PUT',
+      json: data,
+    },
+    [['groups'], ['groupResources']],
+  );
 }
 
 export function createResources(type, data) {
-  return httpQuery({
-    url: `/api/v1/${type}/schema`,
-    method: 'POST',
-    json: data,
-  });
+  return mutateWithInvalidation(
+    {
+      url: `/api/v1/${type}/schema`,
+      method: 'POST',
+      json: data,
+    },
+    [['groupResources'], ['resource']],
+  );
 }
 
 export function editResources(type, group, name, data) {
-  return httpQuery({
-    url: `/api/v1/${type}/schema/${group}/${name}`,
-    method: 'PUT',
-    json: data,
-  });
+  return mutateWithInvalidation(
+    {
+      url: `/api/v1/${type}/schema/${group}/${name}`,
+      method: 'PUT',
+      json: data,
+    },
+    [
+      ['groupResources', type, group],
+      ['resource', type, group, name],
+    ],
+  );
 }
 
 export function getindexRuleList(name) {
-  return httpQuery({
+  return fetchWithQuery(['indexRuleList', name], {
     url: `/api/v1/index-rule/schema/lists/${name}`,
     method: 'GET',
   });
 }
 
 export function getindexRuleBindingList(name) {
-  return httpQuery({
+  return fetchWithQuery(['indexRuleBindingList', name], {
     url: `/api/v1/index-rule-binding/schema/lists/${name}`,
     method: 'GET',
   });
 }
 
 export function getTopNAggregationList(name) {
-  return httpQuery({
+  return fetchWithQuery(['topNAggregationList', name], {
     url: `/api/v1/topn-agg/schema/lists/${name}`,
     method: 'GET',
   });
 }
 
 export function getTopNAggregationData(data) {
-  return httpQuery({
-    url: `/api/v1/measure/topn`,
-    json: data,
-    method: 'POST',
-  });
+  const queryKey = ['topNAggregationData', JSON.stringify(data)];
+  return fetchWithQuery(
+    queryKey,
+    {
+      url: `/api/v1/measure/topn`,
+      json: data,
+      method: 'POST',
+    },
+    { staleTime: 0 },
+  );
 }
 
 export function getSecondaryDataModel(type, group, name) {
-  return httpQuery({
+  return fetchWithQuery(['secondaryDataModel', type, group, name], {
     url: `/api/v1/${type}/schema/${group}/${name}`,
     method: 'GET',
   });
 }
 
 export function createSecondaryDataModel(type, data) {
-  return httpQuery({
-    url: `/api/v1/${type}/schema`,
-    method: 'POST',
-    json: data,
-  });
+  return mutateWithInvalidation(
+    {
+      url: `/api/v1/${type}/schema`,
+      method: 'POST',
+      json: data,
+    },
+    [['secondaryDataModel'], ['groupResources']],
+  );
 }
 
 export function updateSecondaryDataModel(type, group, name, data) {
-  return httpQuery({
-    url: `/api/v1/${type}/schema/${group}/${name}`,
-    method: 'PUT',
-    json: data,
-  });
+  return mutateWithInvalidation(
+    {
+      url: `/api/v1/${type}/schema/${group}/${name}`,
+      method: 'PUT',
+      json: data,
+    },
+    [
+      ['secondaryDataModel', type, group, name],
+      ['groupResources', type, group],
+    ],
+  );
 }
 
 export function deleteSecondaryDataModel(type, group, name) {
-  return httpQuery({
-    url: `/api/v1/${type}/schema/${group}/${name}`,
-    method: 'DELETE',
-  });
+  return mutateWithInvalidation(
+    {
+      url: `/api/v1/${type}/schema/${group}/${name}`,
+      method: 'DELETE',
+    },
+    [
+      ['secondaryDataModel', type, group, name],
+      ['groupResources', type, group],
+    ],
+  );
 }
 
 export function fetchProperties(data) {
-  return httpQuery({
-    url: `/api/v1/property/data/query`,
-    method: 'POST',
-    json: data,
-  });
+  const queryKey = ['properties', JSON.stringify(data)];
+  return fetchWithQuery(
+    queryKey,
+    {
+      url: `/api/v1/property/data/query`,
+      method: 'POST',
+      json: data,
+    },
+    { staleTime: 0 },
+  );
 }
 
 export function deleteProperty(group, name) {
-  return httpQuery({
-    url: `/api/v1/property/schema/${group}/${name}`,
-    method: 'DELETE',
-  });
+  return mutateWithInvalidation(
+    {
+      url: `/api/v1/property/schema/${group}/${name}`,
+      method: 'DELETE',
+    },
+    [['properties'], ['groupResources']],
+  );
 }
 
 export function updateProperty(group, name, data) {
-  return httpQuery({
-    url: `/api/v1/property/schema/${group}/${name}`,
-    method: 'PUT',
-    json: data,
-  });
+  return mutateWithInvalidation(
+    {
+      url: `/api/v1/property/schema/${group}/${name}`,
+      method: 'PUT',
+      json: data,
+    },
+    [['properties'], ['groupResources']],
+  );
 }
 
 export function createProperty(data) {
-  return httpQuery({
-    url: `/api/v1/property/schema`,
-    method: 'POST',
-    json: data,
-  });
+  return mutateWithInvalidation(
+    {
+      url: `/api/v1/property/schema`,
+      method: 'POST',
+      json: data,
+    },
+    [['properties'], ['groupResources']],
+  );
 }
 
 export function applyProperty(group, name, id, data) {
-  return httpQuery({
-    url: `/api/v1/property/data/${group}/${name}/${id}`,
-    method: 'PUT',
-    json: data,
-  });
+  return mutateWithInvalidation(
+    {
+      url: `/api/v1/property/data/${group}/${name}/${id}`,
+      method: 'PUT',
+      json: data,
+    },
+    [['properties'], ['groupResources']],
+  );
 }
 
 export function getTrace(group, name) {
-  return httpQuery({
+  return fetchWithQuery(['trace', group, name], {
     url: `/api/v1/trace/schema/${group}/${name}`,
-    method: 'get',
+    method: 'GET',
   });
 }
 
 export function createTrace(json) {
-  return httpQuery({
-    url: `/api/v1/trace/schema`,
-    method: 'POST',
-    json,
-  });
+  return mutateWithInvalidation(
+    {
+      url: `/api/v1/trace/schema`,
+      method: 'POST',
+      json,
+    },
+    [['trace'], ['groupResources']],
+  );
 }
 
 export function updateTrace(group, name, json) {
-  return httpQuery({
-    url: `/api/v1/trace/schema/${group}/${name}`,
-    json,
-    method: 'PUT',
-  });
+  return mutateWithInvalidation(
+    {
+      url: `/api/v1/trace/schema/${group}/${name}`,
+      json,
+      method: 'PUT',
+    },
+    [['trace', group, name], ['groupResources']],
+  );
 }
 
 export function queryTraces(json) {
-  return httpQuery({
-    url: `/api/v1/trace/data`,
-    json,
-    method: 'POST',
-  });
+  const queryKey = ['traceQuery', JSON.stringify(json)];
+  return fetchWithQuery(
+    queryKey,
+    {
+      url: `/api/v1/trace/data`,
+      json,
+      method: 'POST',
+    },
+    { staleTime: 0 },
+  );
 }
 
 export function executeBydbQLQuery(data) {
-  return httpQuery({
-    url: `/api/v1/bydbql/query`,
-    json: data,
-    method: 'POST',
-  });
+  const queryKey = ['bydbql', JSON.stringify(data)];
+  return fetchWithQuery(
+    queryKey,
+    {
+      url: `/api/v1/bydbql/query`,
+      json: data,
+      method: 'POST',
+    },
+    { staleTime: 0 },
+  );
 }
diff --git a/ui/src/components/BydbQL/Index.vue 
b/ui/src/components/BydbQL/Index.vue
index 09f1588f..419da41e 100644
--- a/ui/src/components/BydbQL/Index.vue
+++ b/ui/src/components/BydbQL/Index.vue
@@ -48,6 +48,7 @@ SELECT * FROM STREAM log in sw_recordsLog TIME > '-30m'`);
   const error = ref(null);
   const executionTime = ref(0);
   const codeMirrorInstance = ref(null);
+  const editorLoading = ref(true);
 
   const hasResult = computed(() => queryResult.value !== null);
   const resultType = computed(() => {
@@ -291,6 +292,7 @@ SELECT * FROM STREAM log in sw_recordsLog TIME > '-30m'`);
       'Cmd-Enter': executeQuery,
     };
     cm.setOption('extraKeys', mergedExtraKeys);
+    editorLoading.value = false;
   }
 
   // Fetch groups and schemas for autocomplete
@@ -564,7 +566,13 @@ SELECT * FROM STREAM log in sw_recordsLog TIME > '-30m'`);
         </div>
       </template>
       <div class="query-input-container">
+        <transition name="fade">
+          <div v-show="editorLoading" class="query-loading-state">
+            <el-skeleton :rows="6" animated />
+          </div>
+        </transition>
         <CodeMirror
+          v-show="!editorLoading"
           v-model="queryText"
           :mode="'bydbql'"
           :lint="false"
@@ -706,9 +714,26 @@ SELECT * FROM STREAM log in sw_recordsLog TIME > '-30m'`);
   }
 
   .query-input-container {
+    position: relative;
     border: 1px solid #dcdfe6;
     border-radius: 4px;
     overflow: hidden;
+    min-height: 150px;
+
+    .query-loading-state {
+      position: absolute;
+      inset: 0;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      padding: 16px;
+      background-color: #f5f7fa;
+      z-index: 1;
+    }
+
+    .query-input {
+      transition: opacity 0.2s ease;
+    }
 
     :deep(.in-coder-panel) {
       height: 150px;
@@ -807,6 +832,16 @@ SELECT * FROM STREAM log in sw_recordsLog TIME > '-30m'`);
     }
   }
 
+  .fade-enter-active,
+  .fade-leave-active {
+    transition: opacity 0.2s ease;
+  }
+
+  .fade-enter-from,
+  .fade-leave-to {
+    opacity: 0;
+  }
+
   @media (max-width: 768px) {
     .card-header {
       flex-direction: column;
diff --git a/ui/src/components/CodeMirror/index.vue 
b/ui/src/components/CodeMirror/index.vue
index f06d0229..73c62cee 100644
--- a/ui/src/components/CodeMirror/index.vue
+++ b/ui/src/components/CodeMirror/index.vue
@@ -226,7 +226,6 @@
     width: 100%;
     height: 100%;
     :deep(.CodeMirror) {
-      border: 1px solid #44475a;
       height: 100%;
       width: 100%;
       .CodeMirror-code {
diff --git a/ui/src/components/common/MeasureAndStreamTable.vue 
b/ui/src/components/common/MeasureAndStreamTable.vue
index 29173339..595fdfbd 100644
--- a/ui/src/components/common/MeasureAndStreamTable.vue
+++ b/ui/src/components/common/MeasureAndStreamTable.vue
@@ -17,7 +17,7 @@
   ~ under the License.
 -->
 <script setup>
-  import { defineProps, ref, computed } from 'vue';
+  import { ref, computed } from 'vue';
 
   const props = defineProps({
     tableData: {
diff --git a/ui/src/main.js b/ui/src/main.js
index 2932bf42..f96e0534 100644
--- a/ui/src/main.js
+++ b/ui/src/main.js
@@ -43,6 +43,7 @@ import 'element-plus/dist/index.css';
 import './styles/custom.scss';
 import './styles/main.scss';
 import * as ElIcon from '@element-plus/icons-vue';
+import { installVueQuery } from './plugins/vue-query';
 
 const app = createApp(App);
 
@@ -90,5 +91,6 @@ app.config.globalProperties.mittBus = new mitt();
 app.use(createPinia());
 app.use(router);
 app.use(ElementPlus);
+installVueQuery(app);
 
 app.mount('#app');
diff --git a/ui/src/plugins/vue-query.js b/ui/src/plugins/vue-query.js
new file mode 100644
index 00000000..96f45e00
--- /dev/null
+++ b/ui/src/plugins/vue-query.js
@@ -0,0 +1,40 @@
+/*
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Apache Software Foundation (ASF) licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query';
+
+export const queryClient = new QueryClient({
+  defaultOptions: {
+    queries: {
+      staleTime: 5 * 60 * 1000,
+      gcTime: 30 * 60 * 1000,
+      refetchOnWindowFocus: false,
+      retry: 1,
+    },
+    mutations: {
+      retry: 0,
+    },
+  },
+});
+
+export function installVueQuery(app) {
+  app.use(VueQueryPlugin, {
+    queryClient,
+  });
+}
diff --git a/ui/vite.config.mjs b/ui/vite.config.mjs
index 42be3350..30b8c204 100644
--- a/ui/vite.config.mjs
+++ b/ui/vite.config.mjs
@@ -25,9 +25,12 @@ import vue from '@vitejs/plugin-vue';
 import { loadEnv } from 'vite';
 
 export default ({ mode }) => {
-  const { VITE_API_PROXY, VITE_MONITOR_PROXY } = loadEnv(mode, process.cwd());
+  const env = loadEnv(mode, process.cwd(), '');
+  const { VITE_API_PROXY, VITE_MONITOR_PROXY } = env;
+  const isProduction = mode === 'production';
 
   return {
+    cacheDir: fileURLToPath(new URL('./node_modules/.vite-ui', 
import.meta.url)),
     plugins: [
       AutoImport({
         resolvers: [ElementPlusResolver()],
@@ -40,8 +43,19 @@ export default ({ mode }) => {
     resolve: {
       alias: {
         '@': fileURLToPath(new URL('./src', import.meta.url)),
+        'vue-demi': 'vue-demi/lib/index.mjs',
       },
     },
+    optimizeDeps: {
+      include: [
+        '@tanstack/vue-query',
+        'codemirror',
+        'echarts',
+        'element-plus/es',
+        'vue-demi',
+        'vue-router',
+      ],
+    },
     css: {
       preprocessorOptions: {
         scss: {
@@ -49,6 +63,44 @@ export default ({ mode }) => {
         },
       },
     },
+    esbuild: {
+      drop: isProduction ? ['console', 'debugger'] : [],
+    },
+    define: {
+      __VUE_OPTIONS_API__: false,
+      __VUE_PROD_DEVTOOLS__: false,
+    },
+    build: {
+      target: 'es2019',
+      cssCodeSplit: true,
+      sourcemap: false,
+      chunkSizeWarningLimit: 1500,
+      rollupOptions: {
+        output: {
+          manualChunks(id) {
+            if (!id.includes('node_modules')) {
+              return;
+            }
+
+            if (id.includes('element-plus')) {
+              return 'element-plus';
+            }
+            if (id.includes('codemirror')) {
+              return 'codemirror';
+            }
+            if (id.includes('echarts')) {
+              return 'echarts';
+            }
+            if (id.includes('@tanstack')) {
+              return 'vue-query';
+            }
+            if (id.includes('vue-router')) {
+              return 'vue-router';
+            }
+          },
+        },
+      },
+    },
     server: {
       proxy: {
         '^/api': {

Reply via email to