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': {