This is an automated email from the ASF dual-hosted git repository.
nicholasjiang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-webui.git
The following commit(s) were added to refs/heads/main by this push:
new ca456f4 [Feature] Support dynamic menu tree integration with query
page (#236)
ca456f4 is described below
commit ca456f4dcad17ede406047330bbf1777d158dfe4
Author: s7monk <[email protected]>
AuthorDate: Wed May 22 21:30:59 2024 +0800
[Feature] Support dynamic menu tree integration with query page (#236)
---
.../query/components/menu-tree/index.module.scss | 34 ++-
.../query/components/menu-tree/index.tsx | 258 ++++++++++++++-------
2 files changed, 201 insertions(+), 91 deletions(-)
diff --git
a/paimon-web-ui/src/views/playground/components/query/components/menu-tree/index.module.scss
b/paimon-web-ui/src/views/playground/components/query/components/menu-tree/index.module.scss
index 536333a..7e692a9 100644
---
a/paimon-web-ui/src/views/playground/components/query/components/menu-tree/index.module.scss
+++
b/paimon-web-ui/src/views/playground/components/query/components/menu-tree/index.module.scss
@@ -18,16 +18,42 @@ under the License. */
.container {
width: 100%;
height: 100%;
+ box-sizing: border-box;
.card {
height: 100%;
- .search {
+ .vertical {
display: flex;
- }
+ flex-direction: column;
+ height: 100%;
+ gap: 8px;
- .icon {
- display: flex;
+ .search {
+ display: flex;
+ }
+
+ .icon {
+ display: flex;
+ }
+
+ .scroll {
+ position: relative;
+ flex: 1;
+ }
+
+ .detail-container {
+ height: 45%;
+ margin-left: -18px;
+ margin-right: -18px;
+
+ .detail-vertical {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ gap: 2px;
+ }
+ }
}
}
}
diff --git
a/paimon-web-ui/src/views/playground/components/query/components/menu-tree/index.tsx
b/paimon-web-ui/src/views/playground/components/query/components/menu-tree/index.tsx
index 552a78b..333069e 100644
---
a/paimon-web-ui/src/views/playground/components/query/components/menu-tree/index.tsx
+++
b/paimon-web-ui/src/views/playground/components/query/components/menu-tree/index.tsx
@@ -15,78 +15,59 @@ KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License. */
-import { CodeSlash, FileTrayFullOutline, Search, ServerOutline } from
'@vicons/ionicons5';
+import {CloseSharp, CodeSlash, FileTrayFullOutline, Search, ServerOutline}
from '@vicons/ionicons5';
+import { useCatalogStore } from '@/store/catalog'
import styles from './index.module.scss'
import { NIcon, type TreeOption } from 'naive-ui';
+import {DatabaseOutlined} from "@vicons/antd";
+import type { DataTypeDTO } from "@/api/models/catalog";
+import {getColumns} from "@/api/models/catalog";
export default defineComponent({
name: 'MenuTree',
setup() {
const { t } = useLocaleHooks()
- const treeVariables = reactive({
- treeData: [
- {
- key: 'paimon',
- label: 'paimon',
- prefix: () =>
- h(NIcon, null, {
- default: () => h(ServerOutline)
- }),
- children: [
- {
- key: 'user',
- label: 'user',
- prefix: () =>
- h(NIcon, null, {
- default: () => h(ServerOutline)
- }),
- children: [
- {
- label: 'user_table',
- key: '1',
- content: 'select * from abc where abc.a="abc";select * from
cba where cba.a="cba";',
- prefix: () =>
- h(NIcon, null, {
- default: () => h(FileTrayFullOutline)
- })
- },
- {
- label: 'people_table',
- key: '2',
- content: 'select * from abc where abc.a="abc";',
- prefix: () =>
- h(NIcon, null, {
- default: () => h(FileTrayFullOutline)
- })
- }
- ]
- },
- {
- key: 'role',
- label: 'role',
- prefix: () =>
- h(NIcon, null, {
- default: () => h(ServerOutline)
- }),
- children: [
- {
- label: 'user_table',
- key: '3',
- content: 'select * from kkk;',
- prefix: () =>
- h(NIcon, null, {
- default: () => h(FileTrayFullOutline)
- })
- },
- ]
- }
- ]
+ const catalogStore = useCatalogStore()
+ const catalogStoreRef = storeToRefs(catalogStore)
+ const [tableColumns, useColumns, { loading }] = getColumns()
+
+ const filterValue = ref('')
+ const selectedKeys = ref([])
+
+ const renderPrefix = ({ option }: { option: TreeOption }) => {
+ let icon = ServerOutline
+ switch (option.type) {
+ case 'catalog':
+ icon = DatabaseOutlined
+ break
+ case 'database':
+ icon = ServerOutline
+ break
+ case 'table':
+ icon = FileTrayFullOutline
+ }
+
+ return h(NIcon, null, {
+ default: () => h(icon)
+ })
+ }
+
+ const onLoadMenu = async (node: TreeOption) => {
+ if (node.type === 'catalog') {
+ node.children = await catalogStore.getDatabasesById(node.key as number)
+ } else {
+ const [catalogId, catalogName, databaseName] = (node.key as
string)?.split(' ') || []
+ const params = {
+ catalogId: Number(catalogId),
+ catalogName,
+ databaseName
}
- ],
- filterValue: '',
- selectedKeys: []
- })
+ node.children = (await catalogStore.getTablesByDataBaseId(params)) ||
[]
+ }
+
+ return Promise.resolve()
+ }
const nodeProps = ({ option }: { option: TreeOption }) => {
return {
@@ -107,9 +88,25 @@ export default defineComponent({
}
}
- const handleTreeSelect = (value: never[], option: { children: any; }[]) =>
{
- if (option[0]?.children) return
- treeVariables.selectedKeys = value
+ const dataNodeProps = ({ option }: { option: TreeOption }) => {
+ return {
+ onClick () {
+ const { type } = option
+ if (type === 'table') {
+ isDetailVisible.value = true
+ const { catalogId, name, ...tableData } =
JSON.parse(option.key?.toString() || '')
+ catalogStore.setCurrentTable({
+ catalogId: Number(catalogId),
+ tableName: name,
+ name,
+ ...tableData
+ })
+ }
+ },
+ }
+ }
+
+ const handleTreeSelect = ({ option }: { option: TreeOption }) => {
}
// mitt - handle tab choose
@@ -161,43 +158,130 @@ export default defineComponent({
}
]) as any
+ const isDetailVisible = ref(true);
+ const handleClose = () => {
+ isDetailVisible.value = !isDetailVisible.value;
+ }
+
+ const onFetchData = async () => {
+ if (catalogStore.currentTable &&
Object.keys(catalogStore.currentTable).length > 0) {
+ useColumns({
+ params: catalogStore.currentTable
+ })
+ }
+ }
+
+ watch(() => catalogStore.currentTable, onFetchData)
+
onMounted(() => {
- mittBus.emit('initTreeData', treeVariables)
+ catalogStore.getAllCatalogs(true)
})
+ onMounted(onFetchData)
+
+ const columns = computed(() => tableColumns.value?.columns || []);
+
+ const getTypePrefix = (dataType: DataTypeDTO) => {
+ const numericTypes = ['INT', 'BIGINT', 'FLOAT', 'DOUBLE', 'DECIMAL',
'NUMERIC'];
+ const type = dataType.type || 'Unknown';
+ if (numericTypes.includes(type)) {
+ return { prefix: '123', color: '#33994A' };
+ } else {
+ return { prefix: 'Aa'};
+ }
+ }
+
return {
t,
- ...toRefs(treeVariables),
+ filterValue,
+ menuList: catalogStoreRef.catalogs,
+ onLoadMenu,
nodeProps,
+ dataNodeProps,
handleTreeSelect,
+ renderPrefix,
+ handleClose,
savedQueryList,
- recordList
+ recordList,
+ currentTable: catalogStoreRef.currentTable,
+ columns,
+ isDetailVisible,
+ selectedKeys,
+ getTypePrefix
}
},
render() {
return (
<div class={styles.container}>
<n-card class={styles.card} content-style={'padding:7px 18px;'}>
- <n-tabs default-value="data" justify-content="space-between"
type="line">
- <n-tab-pane name="data" tab={this.t('playground.data')}>
- <n-space vertical>
- <n-input placeholder={this.t('playground.search')}
style="width: 100%;"
- v-model:value={this.filterValue}
- v-slots={{
- prefix: () => <n-icon component={Search} />
- }}
+ <n-tabs default-value="data" justify-content="space-between"
type="line" style={'height: 100%'}>
+ <n-tab-pane name="data" tab={this.t('playground.data')}
style={'height: 100%'}>
+ <div class={styles.vertical}>
+ <n-input placeholder={this.t('playground.search')}
+ v-model:value={this.filterValue}
+ v-slots={{
+ prefix: () => <n-icon component={Search} />
+ }}
>
</n-input>
- <n-tree
- block-line
- expand-on-click
- selected-keys={this.selectedKeys}
- on-update:selected-keys={this.handleTreeSelect}
- data={this.treeData}
- pattern={this.filterValue}
- node-props={this.nodeProps}
- />
- </n-space>
+ <div class={styles.scroll}>
+ <n-scrollbar style={'position: absolute'}>
+ <n-tree
+ block-line
+ expand-on-click
+ selected-keys={this.selectedKeys}
+ on-update:selected-keys={this.handleTreeSelect}
+ data={this.menuList}
+ pattern={this.filterValue}
+ node-props={this.dataNodeProps}
+ onLoad={this.onLoadMenu}
+ render-prefix={this.renderPrefix}
+ />
+ </n-scrollbar>
+ </div>
+ { this.isDetailVisible && this.currentTable && (
+ <div class={styles['detail-container']}>
+ <n-card style={'border-radius: 0; height: 100%;
border-width: 1.4px 0px 0px 0px;'}
+ content-style={'padding:0;'} >
+ <div class={styles['detail-vertical']}>
+ <n-card style={'border: none;'}
content-style={'padding:16px 16px;'}>
+ <n-space justify="space-between">
+ <span>{this.currentTable.tableName}</span>
+ <n-button
+ text
+ onClick={this.handleClose}
+ v-slots={{
+ icon: () => <n-icon
component={CloseSharp}></n-icon>
+ }}
+ >
+ </n-button>
+ </n-space>
+ </n-card>
+ <n-card style={'border: none; flex:1;'}
content-style={'padding:0px 22px;'}>
+ <div style={'height: 100%; position: relative;'}>
+ <n-scrollbar style={'position: absolute;'}>
+ <n-space vertical>
+ {this.columns.map((column, index) => (
+ <n-space key={index} justify="space-between">
+ <div style={{ display: 'flex', alignItems:
'center' }}>
+ <div style={{ width: '24.7px',
textAlign: 'right', marginRight: '10px',
+ color:
this.getTypePrefix(column.dataType || { type: 'Unknown' }).color }}>
+ {this.getTypePrefix(column.dataType ||
{ type: 'Unknown' }).prefix}
+ </div>
+ <span>{column.field}</span>
+ </div>
+ <span>{typeof column.dataType === 'object'
? column.dataType.type : column.dataType}</span>
+ </n-space>
+ ))}
+ </n-space>
+ </n-scrollbar>
+ </div>
+ </n-card>
+ </div>
+ </n-card>
+ </div>
+ )}
+ </div>
</n-tab-pane>
<n-tab-pane name="saved_query"
tab={this.t('playground.saved_query')}>
<n-space vertical>