This is an automated email from the ASF dual-hosted git repository. morningman pushed a commit to branch branch-1.2-lts in repository https://gitbox.apache.org/repos/asf/doris.git
commit 221149e5900683767ab668d3a7b404b564754b8a Author: htyoung <[email protected]> AuthorDate: Mon Jan 9 08:46:18 2023 +0800 fix(ui): 1. fix component/table can not change pageSize,affect system/query profile/session page etc. (#15533) 2. add antd Table Component missing rowKey property to fit react specification 2. fix system/query profile/session/configuration page maybe lead memory leak when switch these pages fast 3.other grammar fix to fit typescript and react specification Co-authored-by: tongyang.hty <[email protected]> --- ui/src/api/api.ts | 64 +++++--- ui/src/components/table/index.tsx | 48 +++--- ui/src/i18n.tsx | 15 +- ui/src/pages/configuration/index.tsx | 37 +++-- ui/src/pages/logs/index.tsx | 56 ++++--- .../playground/content/components/data-prev.tsx | 85 ++++++----- ui/src/pages/playground/page-side/index.tsx | 20 +-- ui/src/pages/playground/tree/index.tsx | 164 +++++++++++---------- ui/src/pages/query-profile/index.tsx | 80 +++++----- ui/src/pages/session/index.tsx | 45 +++--- ui/src/pages/system/index.tsx | 61 ++++---- ui/src/utils/lazy.tsx | 17 ++- ui/src/utils/utils.ts | 37 ++--- ui/tsconfig.json | 94 ++++++------ 14 files changed, 442 insertions(+), 381 deletions(-) diff --git a/ui/src/api/api.ts b/ui/src/api/api.ts index 935958b3d3..083e44d790 100644 --- a/ui/src/api/api.ts +++ b/ui/src/api/api.ts @@ -18,35 +18,41 @@ import {API_BASE} from 'Constants'; import request from 'Utils/request'; import {Result} from '@src/interfaces/http.interface'; + //login export function login<T>(data: any): Promise<Result<T>> { return request('/rest/v1/login', { method: 'POST', - headers:{Authorization: data.password?`Basic ${btoa(data.username+':'+data.password)}`:`Basic ${btoa(data.username+':')}`}, + headers: {Authorization: data.password ? `Basic ${btoa(data.username + ':' + data.password)}` : `Basic ${btoa(data.username + ':')}`}, }); } + //logout export function logOut<T>(): Promise<Result<T>> { - return request(`/rest/v1/logout`,{ + return request(`/rest/v1/logout`, { method: 'POST', }); } + //home export function getHardwareInfo<T>(): Promise<Result<T>> { - return request(`/rest/v1/hardware_info/fe/`,{ + return request(`/rest/v1/hardware_info/fe/`, { method: 'GET', }); } + //system export function getSystem<T>(data: any): Promise<Result<T>> { - return request(`/rest/v1/system${data.path}`,{ + return request(`/rest/v1/system${data.path}`, { method: 'GET', + ...data }); } + //log export function getLog<T>(data: any): Promise<Result<T>> { let localUrl = '/rest/v1/log'; - if(data.add_verbose){ + if (data.add_verbose) { localUrl = `/rest/v1/log?add_verbose=${data.add_verbose}`; } if (data.del_verbose) { @@ -55,71 +61,81 @@ export function getLog<T>(data: any): Promise<Result<T>> { // if (data.add_verbose && data.del_verbose) { // localUrl += `/rest/v1/log?add_verbose=${data.add_verbose}&&del_verbose=${data.del_verbose}`; // } - return request(localUrl,{ - method: (data.add_verbose || data.del_verbose)?'POST':'GET', + return request(localUrl, { + method: (data.add_verbose || data.del_verbose) ? 'POST' : 'GET', + ...data }); } + //query_profile export function queryProfile<T>(data: any): Promise<Result<T>> { let LocalUrl = '/rest/v1/query_profile/'; - if(data.path){ + if (data.path) { LocalUrl = `/rest/v1/query_profile/${data.path}`; } - return request(LocalUrl); + return request(LocalUrl, data); } + //session export function getSession<T>(data: any): Promise<Result<T>> { - return request('/rest/v1/session'); + return request('/rest/v1/session', data); } + //config export function getConfig<T>(data: any): Promise<Result<T>> { - return request('/rest/v1/config/fe/'); + return request('/rest/v1/config/fe/', data); } + //query begin -export function getDatabaseList<T>(data: any): Promise<Result<T>> { +export function getDatabaseList<T>(data?: any): Promise<Result<T>> { let reURL = `${API_BASE}default_cluster/databases`; - if(data){ - if(data.db_name){ + if (data) { + if (data.db_name) { reURL += `/${data.db_name}/tables`; } - if(data.db_name&&data.tbl_name){ + if (data.db_name && data.tbl_name) { reURL += `/${data.tbl_name}/schema`; } } - return request(reURL); + return request(reURL, data); } + export function doQuery<T>(data: any): Promise<Result<T>> { return request(`/api/query/default_cluster/${data.db_name}`, { - method: 'POST',...data, + method: 'POST', ...data, }); } + export function doUp<T>(data: any): Promise<Result<T>> { let localHeader = { label: data.label, columns: data.columns, - column_separator: data. column_separator + column_separator: data.column_separator } - if(!localHeader.columns){ + if (!localHeader.columns) { delete localHeader.columns } return request(`/api/default_cluster/${data.db_name}/${data.tbl_name}/upload?file_id=${data.file_id}&file_uuid=${data.file_uuid}`, { - method: 'PUT',headers:localHeader, + method: 'PUT', headers: localHeader, }); } + export function getUploadData<T>(data: any): Promise<Result<T>> { let localUrl = `/api/default_cluster/${data.db_name}/${data.tbl_name}/upload` - if(data.preview){ + if (data.preview) { localUrl = `/api/default_cluster/${data.db_name}/${data.tbl_name}/upload?file_id=${data.file_id}&file_uuid=${data.file_uuid}&preview=true` } - return request(localUrl,{ + return request(localUrl, { method: 'GET', }); } + export function deleteUploadData<T>(data: any): Promise<Result<T>> { - return request(`/api/default_cluster/${data.db_name}/${data.tbl_name}/upload?file_id=${data.file_id}&file_uuid=${data.file_uuid}`,{ + return request(`/api/default_cluster/${data.db_name}/${data.tbl_name}/upload?file_id=${data.file_id}&file_uuid=${data.file_uuid}`, { method: 'DELETE', }); } + //query end export const AdHocAPI = { getDatabaseList, @@ -131,4 +147,4 @@ export const AdHocAPI = { getHardwareInfo, getUploadData, deleteUploadData, -}; \ No newline at end of file +}; diff --git a/ui/src/components/table/index.tsx b/ui/src/components/table/index.tsx index 94afff63c3..bd095c8631 100644 --- a/ui/src/components/table/index.tsx +++ b/ui/src/components/table/index.tsx @@ -6,9 +6,9 @@ * 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 @@ -16,54 +16,54 @@ * specific language governing permissions and limitations * under the License. */ - -import React, {useState,useEffect} from 'react'; + +import React, {useState, useEffect} from 'react'; import {Table, Popover, Input} from 'antd'; import {FilterFilled} from '@ant-design/icons'; -import {getColumns, filterTableData} from './table.utils.tsx'; +import {getColumns, filterTableData} from './table.utils'; import './index.less'; export default function SortFilterTable(props: any) { - const {isFilter=false, isSort=false, allTableData, isInner, isSystem=false, path=''} = props; - const [tableData, setTableData] = useState([]); - const [localColumns, setColumns] = useState([]); + const {isFilter = false, isSort = false, allTableData, isInner, isSystem = false, path = '', rowKey} = props; + const [tableData, setTableData] = useState<any[]>([]); + const [localColumns, setColumns] = useState<any[]>([]); // function onChange(pagination, filters, sorter, extra) { // console.log('params', pagination, filters, sorter, extra); // } - function changeTableData(e){ - const localData = filterTableData(allTableData.rows,e.target.value); + function changeTableData(e) { + const localData = filterTableData(allTableData.rows, e.target.value); setTableData(localData); } + const content = ( - <Input placeholder="Filter data" onChange={e=>changeTableData(e)}/> + <Input placeholder="Filter data" onChange={e => changeTableData(e)}/> ); useEffect(() => { - if(allTableData.rows&&allTableData.column_names){ - setColumns(getColumns(allTableData.column_names, isSort, isInner, allTableData.href_columns||allTableData.href_column, path)); + if (allTableData.rows && allTableData.column_names) { + setColumns(getColumns(allTableData.column_names, isSort, isInner, allTableData.href_columns || allTableData.href_column, path)); setTableData(allTableData.rows); } }, [allTableData]); - return( - <span className='systemTable' > - {isFilter?<Popover className={isSystem?'searchSystem':'search'} content={content} trigger="click"> + return ( + <span className='systemTable'> + {isFilter ? <Popover className={isSystem ? 'searchSystem' : 'search'} content={content} trigger="click"> <FilterFilled/> - </Popover>:''} + </Popover> : ''} <Table columns={localColumns} + rowKey={(record) => typeof rowKey === 'function' ? rowKey(record) : (typeof rowKey === 'string' ? rowKey : undefined)} dataSource={tableData} scroll={{ x: 'max-content' }} size='small' bordered // onChange={onChange} pagination={{ - size:'small', - showTotal:(total, range) => `${range[0]}-${range[1]} of ${total} items`, - showSizeChanger:true, - showQuickJumper:true, - hideOnSinglePage:true, - pageSize:30, - defaultPageSize:30, + size: 'small', + showTotal: (total, range) => `${range[0]}-${range[1]} of ${total} items`, + showSizeChanger: true, + showQuickJumper: true, + defaultPageSize: 30, }} /> </span> diff --git a/ui/src/i18n.tsx b/ui/src/i18n.tsx index 331c465a0b..1a0c8676a5 100644 --- a/ui/src/i18n.tsx +++ b/ui/src/i18n.tsx @@ -6,9 +6,9 @@ * 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 @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - + import LanguageDetector from 'i18next-browser-languagedetector'; import i18n from 'i18next'; import enUsTrans from '../public/locales/en-us.json'; @@ -24,8 +24,9 @@ import zhCnTrans from '../public/locales/zh-cn.json'; import { initReactI18next } from 'react-i18next'; -i18n.use(LanguageDetector) - .use(initReactI18next) + +i18n.use(LanguageDetector) + .use(initReactI18next) .init({ lng: localStorage.getItem('I18N_LANGUAGE') || "en", resources: { @@ -39,9 +40,9 @@ i18n.use(LanguageDetector) fallbackLng: 'en', debug: false, interpolation: { - escapeValue: false + escapeValue: false } }); export default i18n; - + diff --git a/ui/src/pages/configuration/index.tsx b/ui/src/pages/configuration/index.tsx index 0152d84039..cafd9f48b5 100644 --- a/ui/src/pages/configuration/index.tsx +++ b/ui/src/pages/configuration/index.tsx @@ -6,9 +6,9 @@ * 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 @@ -16,39 +16,38 @@ * specific language governing permissions and limitations * under the License. */ - -import React, {useState, useEffect} from 'react'; -import {Typography, Button, Row, Col} from 'antd'; -const {Title} = Typography; + +import React, {useEffect, useState} from 'react'; +import {Typography} from 'antd'; import {getConfig} from 'Src/api/api'; import Table from 'Src/components/table'; + +const {Title} = Typography; export default function Configuration(params: any) { - const [allTableData, setAllTableData] = useState({}); - const getConfigData = function(){ - getConfig({}).then(res=>{ + const [allTableData, setAllTableData] = useState<any>({column_names: [], rows: []}); + const getConfigData = function (ac?: AbortController) { + getConfig({signal: ac?.signal}).then(res => { if (res && res.msg === 'success') { setAllTableData(res.data); } - }) - .catch(err=>{ - setAllTableData({ - column_names:[], - rows:[], - }); - }); + }).catch(err => { + }); }; useEffect(() => { - getConfigData(); + const ac = new AbortController(); + getConfigData(ac); + return () => ac.abort(); }, []); - return( - <Typography style={{padding:'30px'}}> + return ( + <Typography style={{padding: '30px'}}> <Title level={2}>Configure Info</Title> <Table isSort={true} isFilter={true} // isInner={true} allTableData={allTableData} + rowKey={record => record.Name} /> </Typography> ); diff --git a/ui/src/pages/logs/index.tsx b/ui/src/pages/logs/index.tsx index 9ca37f2860..065b55d510 100644 --- a/ui/src/pages/logs/index.tsx +++ b/ui/src/pages/logs/index.tsx @@ -6,9 +6,9 @@ * 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 @@ -16,36 +16,44 @@ * specific language governing permissions and limitations * under the License. */ - -import React,{useState, useEffect, useRef} from 'react'; -import {Typography, Divider, Row, Col, Input, BackTop} from 'antd'; -const {Title, Paragraph, Text} = Typography; + +import React, {useEffect, useRef, useState} from 'react'; +import {BackTop, Col, Divider, Input, Row, Typography} from 'antd'; import {getLog} from 'Src/api/api'; +import {Result} from "@src/interfaces/http.interface"; + +const {Title, Paragraph} = Typography; const {Search} = Input; -import {Result} from '@src/interfaces/http.interface'; export default function Logs(params: any) { - const container = useRef(); - const [LogConfiguration, setLogConfiguration] = useState({}); - const [LogContents, setLogContents] = useState({}); - function getLogData(data){ - getLog(data).then(res=>{ - if(res.data && res.msg === 'success'){ - if(res.data.LogConfiguration){ + const container = useRef<HTMLDivElement>(null); + const [LogConfiguration, setLogConfiguration] = useState<any>({}); + const [LogContents, setLogContents] = useState<any>({}); + + function getLogData(data) { + getLog(data).then((res: Result<any>) => { + if (res.data && res.msg === 'success') { + if (res.data.LogConfiguration) { setLogConfiguration(res.data.LogConfiguration); } - if(res.data.LogContents){ - container.current.innerHTML=res.data.LogContents.log; + if (res.data.LogContents) { + if (container.current !== null) { + container.current.innerHTML = res.data.LogContents.log; + } setLogContents(res.data.LogContents); } } + }).catch(err => { }); } + useEffect(() => { - getLogData({}); + const ac = new AbortController(); + getLogData({signal: ac.signal}); + return () => ac.abort(); }, []); - return( - <Typography style={{padding:'30px'}}> - <Title >Log Configuration</Title> + return ( + <Typography style={{padding: '30px'}}> + <Title>Log Configuration</Title> <Paragraph> <p>Level: {LogConfiguration.VerboseNames}</p> <p>Verbose Names:{LogConfiguration.VerboseNames}</p> @@ -56,24 +64,24 @@ export default function Logs(params: any) { <Search placeholder="new verbose name" enterButton="Add" - onSearch={value => getLogData({add_verbose:value})} + onSearch={value => getLogData({add_verbose: value})} /> </Col> <Col span={4} offset={1}> <Search placeholder="del verbose name" enterButton="Delete" - onSearch={value => getLogData({del_verbose:value})} + onSearch={value => getLogData({del_verbose: value})} /> </Col> </Row> <Divider/> - <Title style={{marginTop:'0px'}}>Log Contents</Title> + <Title style={{marginTop: '0px'}}>Log Contents</Title> <Paragraph> <p>Log path is: {LogContents.logPath}</p> <p>{LogContents.showingLast}</p> </Paragraph> - <div ref={container} style={{background: '#f9f9f9',padding: '20px'}}> + <div ref={container} style={{background: '#f9f9f9', padding: '20px'}}> {/* {LogContents.log} */} </div> <BackTop></BackTop> diff --git a/ui/src/pages/playground/content/components/data-prev.tsx b/ui/src/pages/playground/content/components/data-prev.tsx index d3856848ce..8d027c34e2 100644 --- a/ui/src/pages/playground/content/components/data-prev.tsx +++ b/ui/src/pages/playground/content/components/data-prev.tsx @@ -6,9 +6,9 @@ * 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 @@ -16,65 +16,73 @@ * specific language governing permissions and limitations * under the License. */ - -import React,{useState,useEffect} from 'react'; + +import React, {useEffect, useState} from 'react'; import {AdHocAPI} from 'Src/api/api'; import {getDbName} from 'Utils/utils'; -import {Row, Empty, notification, Table} from 'antd'; +import {notification, Row, Table} from 'antd'; import {FlatBtn} from 'Components/flatbtn'; import {useTranslation} from 'react-i18next'; + export function DataPrev(props: any) { - let { t } = useTranslation(); - const {db_name,tbl_name} = getDbName(); - const [tableData,setTableData] = useState([]); - const [columns,setColumns] = useState([]); + let {t} = useTranslation(); + const {db_name, tbl_name} = getDbName(); + const [tableData, setTableData] = useState<any[]>([]); + const [columns, setColumns] = useState<any[]>([]); + function toQuery(): void { - if (!tbl_name){ - notification.error({message: t('selectWarning')}); + if (!tbl_name) { + notification.error({message: t<string>('selectWarning')}); return; } AdHocAPI.doQuery({ db_name, - body:{stmt:`SELECT * FROM ${db_name}.${tbl_name} LIMIT 10`}, - }).then(res=>{ + body: {stmt: `SELECT * FROM ${db_name}.${tbl_name} LIMIT 10`}, + }).then((res: any) => { if (res && res.msg === 'success') { - console.log(getColumns(res.data?.meta),2222) + console.log(getColumns(res.data?.meta), 2222) setColumns(getColumns(res.data?.meta)) - setTableData(getTabledata(res.data)); + setTableData(getTableData(res.data)); } - }) - .catch(()=>{ + }).catch(() => { setTableData([]); }); } - function getColumns(params: string[]) { - console.log(params,2222) - if(!params||params.length === 0){return [];} - - let arr = params.map(item=> { + + function getColumns(params: any[]) { + if (!params || params.length === 0) { + return []; + } + + let arr: any[] = params.map(item => { return { title: item.name, dataIndex: item.name, key: item.name, width: 150, - render:(text, record, index)=>{return text === '\\N' ? '-' : text} + render: (text, record, index) => { + return text === '\\N' ? '-' : text + } }; }); return arr; } - function getTabledata(data){ - let meta = data.meta; + + function getTableData(data): any[] { + let meta = data.meta; let source = data.data; - let res = []; - if(!source||source.length === 0){return [];} - let metaArr = meta.map(item=>item.name) - for (let i=0;i<source.length;i++) { + let res: any[] = []; + if (!source || source.length === 0) { + return []; + } + let metaArr = meta.map(item => item.name) + for (let i = 0; i < source.length; i++) { let node = source[i]; - if(node.length !== meta.length){ - return {} + if (node.length !== meta.length) { + return [] } let obj = {} - metaArr.map((item,idx)=>{ + metaArr.map((item, idx) => { obj[item] = node[idx] }) obj['key'] = i @@ -82,13 +90,14 @@ export function DataPrev(props: any) { } return res; } - useEffect(()=>{ + + useEffect(() => { toQuery(); - },[location.pathname]); + }, [location.pathname]); return ( <div> <Row justify="space-between" style={{marginBottom: 10}}> - <span style={{paddingBottom:'15px'}}>{t('dataPreview')}({t('display10')})</span> + <span style={{paddingBottom: '15px'}}>{t('dataPreview') + "(" + t('display10') + ")"}</span> <span> {db_name}.{tbl_name} </span> @@ -103,13 +112,13 @@ export function DataPrev(props: any) { <Table bordered columns={columns} - style={{maxWidth:' calc(100vw - 350px)'}} + style={{maxWidth: ' calc(100vw - 350px)'}} // scroll={{ x:'500', y: '36vh'}} - scroll={{ x: 1500, y: 300 }} + scroll={{x: 1500, y: 300}} dataSource={tableData} size="small" /> - </div> + </div> ); } diff --git a/ui/src/pages/playground/page-side/index.tsx b/ui/src/pages/playground/page-side/index.tsx index 10f3b4a9c4..3347621176 100644 --- a/ui/src/pages/playground/page-side/index.tsx +++ b/ui/src/pages/playground/page-side/index.tsx @@ -6,9 +6,9 @@ * 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 @@ -16,26 +16,27 @@ * specific language governing permissions and limitations * under the License. */ - -import React, {useState, useEffect, SyntheticEvent} from 'react'; + +import React, {SyntheticEvent, useState} from 'react'; import {ResizableBox} from 'react-resizable'; -require('react-resizable/css/styles.css'); import styles from './index.less'; +require('react-resizable/css/styles.css'); + export function PageSide(props: any) { const {children} = props; - const [sideBoxWidth,setSideBoxWidth] = useState(300); + const [sideBoxWidth, setSideBoxWidth] = useState(300); - const onResize=function(e: SyntheticEvent, data: any) { + const onResize = function (e: SyntheticEvent, data: any) { const width = data.size.width || 300; setSideBoxWidth(width); }; return ( - <div className={styles['side']} > + <div className={styles['side']}> <ResizableBox width={sideBoxWidth} height={Infinity} - style={{'minHeight':'calc(100vh - 64px)','overflow':'hidden'}} + style={{'minHeight': 'calc(100vh - 64px)', 'overflow': 'hidden'}} resizeHandles={['e']} onResizeStart={onResize} minConstraints={[300, 300]} @@ -45,7 +46,6 @@ export function PageSide(props: any) { {children} </ResizableBox> </div> - ); } diff --git a/ui/src/pages/playground/tree/index.tsx b/ui/src/pages/playground/tree/index.tsx index 0f0d440893..e3f56f98e7 100644 --- a/ui/src/pages/playground/tree/index.tsx +++ b/ui/src/pages/playground/tree/index.tsx @@ -6,9 +6,9 @@ * 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 @@ -16,23 +16,25 @@ * specific language governing permissions and limitations * under the License. */ -import React, {useState,useEffect} from 'react'; -import {Tree, Spin, Input} from 'antd'; -const { Search } = Input; -import {TableOutlined, HddOutlined, ReloadOutlined} from '@ant-design/icons'; +import React, {useEffect, useState} from 'react'; +import {Input, Spin, Tree} from 'antd'; +import {HddOutlined, ReloadOutlined, TableOutlined} from '@ant-design/icons'; import {AdHocAPI} from 'Src/api/api'; import {useTranslation} from 'react-i18next'; -import { - AdhocContentRouteKeyEnum, -} from '../adhoc.data'; +import {AdhocContentRouteKeyEnum,} from '../adhoc.data'; +import './index.css'; + +const {Search} = Input; + interface DataNode { - title: string; - key: string; - isLeaf?: boolean; - children?: DataNode[]; + title: string; + key: string; + isLeaf?: boolean; + children?: DataNode[]; } -import './index.css'; + const initTreeDate: DataNode[] = []; + function updateTreeData(list: DataNode[], key, children) { return list.map(node => { if (node.key === key) { @@ -40,26 +42,30 @@ function updateTreeData(list: DataNode[], key, children) { ...node, children, }; - } + } return node; }); } + export function AdHocTree(props: any) { - let { t } = useTranslation(); + let {t} = useTranslation(); const [treeData, setTreeData] = useState(initTreeDate); const [realTree, setRealTree] = useState(initTreeDate); const [loading, setLoading] = useState(true); - const [expandedKeys, setExpandedKeys] = useState([]); - const [searchValue, setSearchValue] = useState(''); + const [expandedKeys, setExpandedKeys] = useState<any[]>([]); const [autoExpandParent, setAutoExpandParent] = useState(true); + useEffect(() => { - initTreeData() + const ac = new AbortController(); + initTreeData(ac); + return () => ac.abort(); }, []); - function initTreeData(){ - AdHocAPI.getDatabaseList().then(res=>{ + + function initTreeData(ac?: AbortController) { + AdHocAPI.getDatabaseList({signal: ac?.signal}).then(res => { if (res.msg === 'success' && Array.isArray(res.data)) { const num = Math.random() - const treeData = res.data.map((item,index)=>{ + const treeData = res.data.map((item, index) => { return { title: item, key: `${num}-1-${index}-${item}`, @@ -70,34 +76,35 @@ export function AdHocTree(props: any) { getRealTree(treeData); } setLoading(false); + }).catch(err => { }); } + function onLoadData({key, children}) { - const [random, storey, index, db_name] = key.split('-'); + const [, storey, , db_name] = key.split('-'); const param = { db_name, // tbl_name, }; - return AdHocAPI.getDatabaseList(param).then(res=>{ - if (res.msg=='success' && Array.isArray(res.data)) { - const children = res.data.map((item,index)=>{ - if (storey === '1'){ + return AdHocAPI.getDatabaseList(param).then(res => { + if (res.msg == 'success' && Array.isArray(res.data)) { + const children = res.data.map((item, index) => { + if (storey === '1') { return { title: item, key: `2-${index}-${param.db_name}-${item}`, - icon: <TableOutlined />, + icon: <TableOutlined/>, isLeaf: true, }; } - }); const trData = updateTreeData(treeData, key, children); setTreeData(trData); getRealTree(trData); } }); - } + function handleTreeSelect( keys: React.ReactText[], info: any, @@ -107,73 +114,79 @@ export function AdHocTree(props: any) { props.history.push(`/Playground/${path}/${keys[0].split(':')[1]}`); } } - function onSearch(e){ - const { value } = e.target; - const expandedKeys = treeData - .map((item, index) => { - if (getParentKey(value, treeData[index].children, index)) { - return item.key - } else { - return null; - } - }) + + function onSearch(e) { + const {value} = e.target; + const expandedKeys: any[] = treeData + .map((item, index) => { + if (getParentKey(value, treeData[index].children, index)) { + return item.key + } else { + return null; + } + }) setExpandedKeys(expandedKeys); - setSearchValue(value); setAutoExpandParent(true); getRealTree(treeData, value); - }; + } + function onExpand(expandedKeys) { setExpandedKeys(expandedKeys); setAutoExpandParent(false); - }; + } + const getParentKey = (key, tree, idx) => { if (!tree) { return false; } for (let i = 0; i < tree.length; i++) { - const node = tree[i]; - if (node.title.includes(key)) { - return true - } else { - treeData[idx].children ? treeData[idx].children[i].title = node.title : '' - } + const node = tree[i]; + if (node.title.includes(key)) { + return true + } else { + treeData[idx].children ? treeData[idx].children[i].title = node.title : '' + } } return false; }; - function getRealTree(treeData, value){ - const realTree = inner(treeData); - function inner(treeData){ + + function getRealTree(treeData, value?) { + const realTree = inner(treeData); + + function inner(treeData) { return treeData.map(item => { const search = value || ''; const index = item.title.indexOf(search); const beforeStr = item.title.substr(0, index); const afterStr = item.title.substr(index + search.length); const title = - index > -1 ? ( - <span> + index > -1 ? ( + <span> {beforeStr} - <span className="site-tree-search-value">{search}</span> - {afterStr} + <span className="site-tree-search-value">{search}</span> + {afterStr} </span> - ) : ( - item.title - ); + ) : ( + item.title + ); if (item.children) { - return {...item, title, children: inner(item.children)}; + return {...item, title, children: inner(item.children)}; } return { - ...item, - title + ...item, + title }; }); } - debounce(setRealTree(realTree),300); + + debounce(setRealTree(realTree), 300); } + function debounce(fn, wait) { - var timer = null; + let timer = null; return function () { - var context = this - var args = arguments + let context = this + let args = arguments if (timer) { clearTimeout(timer); timer = null; @@ -183,19 +196,20 @@ export function AdHocTree(props: any) { }, wait) } } + return ( <> <Spin spinning={loading} size="small"/> <div> - <Search - size="small" - style={{ padding: 5, position: 'fixed', zIndex: '99', width: '300px'}} - placeholder={t('search')} - enterButton={<ReloadOutlined />} + <Search + size="small" + style={{padding: 5, position: 'fixed', zIndex: '99', width: '300px'}} + placeholder={t('search')} + enterButton={<ReloadOutlined/>} onSearch={initTreeData} - onChange={onSearch} /> + onChange={onSearch}/> </div> - + <Tree showIcon={true} loadData={onLoadData} @@ -203,12 +217,12 @@ export function AdHocTree(props: any) { onExpand={onExpand} expandedKeys={expandedKeys} autoExpandParent={autoExpandParent} - style={{'width':'100%', height:'86vh' ,paddingTop:'35px',overflowY:'scroll'}} + style={{'width': '100%', height: '86vh', paddingTop: '35px', overflowY: 'scroll'}} onSelect={(selectedKeys, info) => handleTreeSelect( selectedKeys, info, - AdhocContentRouteKeyEnum.Structure ) + AdhocContentRouteKeyEnum.Structure) } /> </> diff --git a/ui/src/pages/query-profile/index.tsx b/ui/src/pages/query-profile/index.tsx index 2b296cd233..ccdf64ce26 100644 --- a/ui/src/pages/query-profile/index.tsx +++ b/ui/src/pages/query-profile/index.tsx @@ -6,9 +6,9 @@ * 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 @@ -16,74 +16,82 @@ * specific language governing permissions and limitations * under the License. */ - -import React, {useState, useEffect, useRef} from 'react'; -import {Typography, Button, Row, Col} from 'antd'; -const {Text, Title, Paragraph} = Typography; + +import React, {useEffect, useRef, useState} from 'react'; +import {Button, Col, Row, Typography} from 'antd'; import {queryProfile} from 'Src/api/api'; import Table from 'Src/components/table'; import {useHistory} from 'react-router-dom'; +import {Result} from '@src/interfaces/http.interface'; + +const {Text, Title} = Typography; export default function QueryProfile(params: any) { // const [parentUrl, setParentUrl] = useState(''); - const container = useRef(); - const [allTableData, setAllTableData] = useState({}); - const [profile, setProfile] = useState(); + const container = useRef<HTMLDivElement>(null); + const [allTableData, setAllTableData] = useState({column_names: [], rows: []}); + const [profile, setProfile] = useState<any>(); const history = useHistory(); - const doQueryProfile = function(){ + const doQueryProfile = function (ac?: AbortController) { const param = { path: getLastPath(), + signal: ac?.signal }; - queryProfile(param).then(res=>{ + queryProfile(param).then((res: Result<any>) => { if (res && res.msg === 'success') { - if(!res.data.column_names){ + if (!res.data.column_names) { setProfile(res.data); - container.current.innerHTML=res.data; - }else{ + if (container.current !== null) { + container.current.innerHTML = res.data; + } + } else { setProfile(''); setAllTableData(res.data); } } else { setAllTableData({ - column_names:[], - rows:[], + column_names: [], + rows: [], }); } - }) - .catch(err=>{ - setAllTableData({ - column_names:[], - rows:[], - }); - }); + }).catch(err => { + }); }; useEffect(() => { - doQueryProfile(); + const ac = new AbortController(); + doQueryProfile(ac); + return () => ac.abort(); }, [location.pathname]); - function getLastPath(){ + + function getLastPath() { let arr = location.pathname.split('/'); let str = arr.pop(); return str === 'QueryProfile' ? '' : str; } - function goPrev(){ - if (location.pathname === '/QueryProfile/') {return;} + + function goPrev() { + if (location.pathname === '/QueryProfile/') { + return; + } history.push('/QueryProfile/'); } - return( - <Typography style={{padding:'30px'}}> - <Title >Finished Queries</Title> - <Row style={{paddingBottom:'15px'}}> - <Col span={12} ><Text type="strong">This table lists the latest 100 queries</Text></Col> - <Col span={12} style={{textAlign:'right'}}> - {profile?<Button type="primary" onClick={goPrev}>back</Button>:''} + return ( + <Typography style={{padding: '30px'}}> + <Title>Finished Queries</Title> + + <Row style={{paddingBottom: '15px'}}> + <Col span={12}><Text strong={true}>This table lists the latest 100 queries</Text></Col> + <Col span={12} style={{textAlign: 'right'}}> + {profile ? <Button type="primary" onClick={goPrev}>back</Button> : ''} </Col> </Row> { profile - ?<div ref={container} style={{background: '#f9f9f9',padding: '20px'}}> + ? <div ref={container} style={{background: '#f9f9f9', padding: '20px'}}> {/* {profile} */} </div> - :<Table + : <Table + rowKey={(record) => record['Query ID']} isSort={true} isFilter={true} isInner={'/QueryProfile'} diff --git a/ui/src/pages/session/index.tsx b/ui/src/pages/session/index.tsx index 393cf324d7..4c97c095be 100644 --- a/ui/src/pages/session/index.tsx +++ b/ui/src/pages/session/index.tsx @@ -6,9 +6,9 @@ * 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 @@ -16,47 +16,46 @@ * specific language governing permissions and limitations * under the License. */ - -import React, {useState, useEffect} from 'react'; -import {Typography, Button, Row, Col} from 'antd'; -const {Text, Title, Paragraph} = Typography; + +import React, {useEffect, useState} from 'react'; +import {Row, Typography} from 'antd'; import {getSession} from 'Src/api/api'; import Table from 'Src/components/table'; + +const {Text, Title} = Typography; // import {useHistory} from 'react-router-dom'; export default function Session(params: any) { // const [parentUrl, setParentUrl] = useState(''); - const [allTableData, setAllTableData] = useState({}); + const [allTableData, setAllTableData] = useState<any>({column_names: [], rows: []}); // const history = useHistory(); - const getSessionData = function(){ - getSession({}).then(res=>{ + const getSessionData = function (ac: AbortController) { + getSession({signal: ac.signal}).then(res => { if (res && res.msg === 'success') { setAllTableData(res.data); } - }) - .catch(err=>{ - setAllTableData({ - column_names:[], - rows:[], - }); - }); + }).catch(err => { + }); }; useEffect(() => { - getSessionData(); + const ac = new AbortController(); + getSessionData(ac); + return () => ac.abort(); }, []); - return( - <Typography style={{padding:'30px'}}> - <Title >Session Info</Title> + return ( + <Typography style={{padding: '30px'}}> + <Title>Session Info</Title> - <Row style={{paddingBottom:'15px'}}> - <Text type="strong">This page lists the session info, there are {allTableData?.rows?.length} active sessions.</Text> + <Row style={{paddingBottom: '15px'}}> + <Text strong={true}>This page lists the session info, there are {allTableData?.rows?.length} active + sessions.</Text> </Row> <Table isSort={true} isFilter={true} // isInner={true} allTableData={allTableData} + rowKey={(record) => record.Id} /> - </Typography> ); } diff --git a/ui/src/pages/system/index.tsx b/ui/src/pages/system/index.tsx index ce96744a5b..f03c366264 100644 --- a/ui/src/pages/system/index.tsx +++ b/ui/src/pages/system/index.tsx @@ -6,9 +6,9 @@ * 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 @@ -16,45 +16,48 @@ * specific language governing permissions and limitations * under the License. */ - -import React, {useState, useEffect} from 'react'; -import {Typography, Button, Row, Col} from 'antd'; -const {Text, Title, Paragraph} = Typography; + +import React, {useEffect, useState} from 'react'; +import {Button, Col, Row, Typography} from 'antd'; import {getSystem} from 'Src/api/api'; import Table from 'Src/components/table'; import {useHistory} from 'react-router-dom'; +import {Result} from '@src/interfaces/http.interface'; + +const {Text, Title, Paragraph} = Typography; + export default function System(params: any) { - const [parentUrl, setParentUrl] = useState(''); - const [allTableData, setAllTableData] = useState({}); + const [parentUrl, setParentUrl] = useState<string>(''); + const [allTableData, setAllTableData] = useState<any>({column_names: [], rows: []}); const history = useHistory(); - const getSystemData = function(){ + const getSystemData = function (ac?: any) { const param = { - path:location.search, + path: location.search, + signal: ac?.signal, }; - getSystem(param).then(res=>{ + getSystem(param).then((res: Result<any>) => { if (res && res.msg === 'success') { setAllTableData(res.data); setParentUrl(res.data.parent_url); } else { setAllTableData({ - column_names:[], - rows:[], + column_names: [], + rows: [], }); } - }) - .catch(err=>{ - setAllTableData({ - column_names:[], - rows:[], - }); - }); + }).catch(err => { + }); }; + useEffect(() => { - getSystemData(); + const ac = new AbortController(); + getSystemData(ac); + return () => ac.abort(); }, [location.search]); - function goPrev(){ + + function goPrev() { if (parentUrl === '/rest/v1/system') { history.push('/System?path=/'); return; @@ -63,16 +66,17 @@ export default function System(params: any) { history.push(parentUrl.split('v1/')[1]); } } - return( - <Typography style={{padding:'30px'}}> + + return ( + <Typography style={{padding: '30px'}}> <Title level={2}>System Info</Title> - <Text type="strong">This page lists the system info, like /proc in Linux.</Text> + <Text strong={true}>This page lists the system info, like /proc in Linux.</Text> <Paragraph> </Paragraph> - <Row style={{paddingBottom:'15px'}}> - <Col span={12} style={{color:'#02a0f9'}}>Current path: {location.search.split('=')[1]}</Col> - <Col span={12} style={{textAlign:'right'}}> + <Row style={{paddingBottom: '15px'}}> + <Col span={12} style={{color: '#02a0f9'}}>Current path: {location.search.split('=')[1]}</Col> + <Col span={12} style={{textAlign: 'right'}}> <Button size='small' type="primary" onClick={goPrev}>Parent Dir</Button> </Col> </Row> @@ -84,6 +88,7 @@ export default function System(params: any) { path = 'System' isSystem = {true} allTableData={allTableData} + rowKey={(record) => record.name} /> </Typography> ); diff --git a/ui/src/utils/lazy.tsx b/ui/src/utils/lazy.tsx index df9d483c60..f031556eea 100644 --- a/ui/src/utils/lazy.tsx +++ b/ui/src/utils/lazy.tsx @@ -6,9 +6,9 @@ * 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 @@ -16,23 +16,24 @@ * specific language governing permissions and limitations * under the License. */ - + import React, {Component} from 'react'; const asyncComponent = importComponent => { - return class extends Component { + return class extends Component<{}, { component: any }> { constructor(props) { super(props); this.state = { component: null }; } + componentDidMount() { - importComponent() - .then(cmp => { - this.setState({component: cmp.default}); - }); + importComponent().then(cmp => { + this.setState({component: cmp.default}); + }); } + render() { const C = this.state.component; return C ? <C {...this.props}/> : null; diff --git a/ui/src/utils/utils.ts b/ui/src/utils/utils.ts index 584c288edc..eeb5b657f9 100644 --- a/ui/src/utils/utils.ts +++ b/ui/src/utils/utils.ts @@ -27,16 +27,14 @@ function isSuccess(response) { let {code, msg} = response; - if (code === 0 && msg === 'success') { - return true - } - return false; + return code === 0 && msg === 'success'; } -function getDbName(params) { + +function getDbName(params?: any) { const infoArr = location.pathname.split('/'); - const str = infoArr[infoArr.length-1]; - const res = {}; - if(str && str !=='Playground'){ + const str = infoArr[infoArr.length - 1]; + const res: any = {}; + if (str && str !== 'Playground') { const db_name = str.split('-')[0]; const tbl_name = str.split('-')[1]; res.db_name = db_name; @@ -44,10 +42,11 @@ function getDbName(params) { } return res; } + function getTimeNow() { let dateNow = new Date(); let fmt = 'yyyy-MM-dd hh:mm:ss'; - var o = { + let o = { 'M+': dateNow.getMonth() + 1, 'd+': dateNow.getDate(), 'h+': dateNow.getHours(), @@ -58,29 +57,31 @@ function getTimeNow() { }; if (/(y+)/.test(fmt)) { fmt = fmt.replace( - RegExp.$1, - (dateNow.getFullYear() + '').substr(4 - RegExp.$1.length) + RegExp.$1, + (dateNow.getFullYear() + '').substr(4 - RegExp.$1.length) ); } - for (var k in o) { + for (let k in o) { if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace( RegExp.$1, RegExp.$1.length === 1 - ? o[k] - : ('00' + o[k]).substr(('' + o[k]).length) + ? o[k] + : ('00' + o[k]).substr(('' + o[k]).length) ); } } return fmt; } -function getBasePath(){ + +function getBasePath() { let arr = location.pathname.split('/'); let res = ''; - if(arr.length>5){ - arr = arr.slice(0,5); + if (arr.length > 5) { + arr = arr.slice(0, 5); res = arr.join('/'); } return res; } -module.exports = {isSuccess, getDbName, getTimeNow, getBasePath}; \ No newline at end of file + +export {isSuccess, getDbName, getTimeNow, getBasePath}; diff --git a/ui/tsconfig.json b/ui/tsconfig.json index 0f07e067e8..19d16e4e61 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -1,29 +1,30 @@ { - "compilerOptions": { - "outDir": "dist", - "module": "commonjs", - "target": "es5", - "lib": ["es7", "dom", "es2015.iterable", "es2019"], - "sourceMap": true, - "allowJs": true, - "jsx": "react", - "moduleResolution": "node", - "forceConsistentCasingInFileNames": false, - "noImplicitReturns": true, - "noImplicitThis": true, - "noImplicitAny": false, - "strictNullChecks": true, - "esModuleInterop": true, - "suppressImplicitAnyIndexErrors": true, - "noUnusedLocals": true, - "allowSyntheticDefaultImports": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "noImplicitUseStrict": true, - "alwaysStrict": false, - "downlevelIteration": true, - "baseUrl": "./", - "paths": { + "compilerOptions": { + "outDir": "dist", + "module": "commonjs", + "target": "es5", + "lib": ["es7", "dom", "es2015.iterable", "es2019"], + "sourceMap": true, + "allowJs": true, + "jsx": "react", + "moduleResolution": "node", + "forceConsistentCasingInFileNames": false, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": false, + "strictNullChecks": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "suppressImplicitAnyIndexErrors": true, + "noUnusedLocals": true, + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "noImplicitUseStrict": true, + "alwaysStrict": false, + "downlevelIteration": true, + "baseUrl": "./", + "paths": { "Components/*": ["src/components/*"], "Src": ["src"], "Utils/*": ["src/utils/*"], @@ -32,28 +33,27 @@ "Constants": ["src/constants"], "@hooks/*": ["src/hooks/*"], "@src/*": ["src/*"] - }, - "typeRoots": [ - "./node_modules/@types", - "custom_typings", - "./typings/**/*.d.ts" - ] }, - "exclude": [ - "build", - "scripts", - "webpack", - "jest", - "bin", - "runtime", - "view", - "public_gen", - "public", - "node_modules", - "coverage", - ".vscode" - ], + "typeRoots": [ + "./node_modules/@types", + "custom_typings", + "./typings/**/*.d.ts" + ] + }, + "exclude": [ + "build", + "scripts", + "webpack", + "jest", + "bin", + "runtime", + "view", + "public_gen", + "public", + "node_modules", + "coverage", + ".vscode" + ], "includes": ["src/**/*.ts", "src/**/*.tsx", "global.d.ts"], "types": ["typePatches"] - } - \ No newline at end of file +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
