diff --git a/app/config/locale/en-US.json b/app/config/locale/en-US.json index b7ad7aaa..b8041f98 100644 --- a/app/config/locale/en-US.json +++ b/app/config/locale/en-US.json @@ -374,7 +374,14 @@ "geography(linestring)Format": "Supported data inserting methods:
Call function ST_GeogFromText('LINESTRING()'), for example:ST_GeogFromText('LINESTRING(3 4,10 50,20 25)')", "geography(polygon)Format": "Supported data inserting methods:
Call function ST_GeogFromText('POLYGON()'), for example:ST_GeogFromText('POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2))')", "durationFormat": "Supported data inserting methods:
Call function duration(), for example:duration({years: 1, seconds: 0})", - "setTTL": "Set TTL (Time To Live)" + "setTTL": "Set TTL (Time To Live)", + "refresh": "Refresh", + "lastRefreshTime": "Last refreshed time", + "statsType": "Type", + "statsName": "Name", + "statsCount": "Count", + "statError": "Update Failed, Please try again.", + "statFinished": "Statistics end" }, "menu": { "use": "Use Manual", diff --git a/app/config/locale/zh-CN.json b/app/config/locale/zh-CN.json index 25fb3723..86e3c92c 100644 --- a/app/config/locale/zh-CN.json +++ b/app/config/locale/zh-CN.json @@ -370,7 +370,14 @@ "geography(linestring)Format": "geo(linestring) 类型支持插入方式:
调用函数 ST_GeogFromText('LINESTRING()'),例如:ST_GeogFromText('LINESTRING(3 4,10 50,20 25)')", "geography(polygon)Format": "geo(polygon) 类型支持插入方式:
调用函数 ST_GeogFromText('POLYGON()'),例如:ST_GeogFromText('POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2))')", "durationFormat": "duration 类型支持插入方式:
调用函数 duration(),例如:duration({years: 1, seconds: 0})", - "setTTL": "设置TTL(存活时间)" + "setTTL": "设置TTL(存活时间)", + "refresh": "更新", + "lastRefreshTime": "上次更新时间", + "statsType": "维度", + "statsName": "名称", + "statsCount": "数量", + "statError": "统计失败,请重试", + "statFinished": "统计结束" }, "menu": { "use": "使用手册", diff --git a/app/interfaces/import.ts b/app/interfaces/import.ts index 7c888935..30e4f69e 100644 --- a/app/interfaces/import.ts +++ b/app/interfaces/import.ts @@ -1,10 +1,10 @@ export enum ITaskStatus { - 'statusFinished' = 'statusFinished', - 'statusStoped' = 'statusStoped', - 'statusProcessing' = 'statusProcessing', - 'statusNotExisted' = 'statusNotExisted', - 'statusAborted' = 'statusAborted', + 'StatusFinished' = 'statusFinished', + 'StatusStoped' = 'statusStoped', + 'StatusProcessing' = 'statusProcessing', + 'StatusNotExisted' = 'statusNotExisted', + 'StatusAborted' = 'statusAborted', } export interface ITaskStats { diff --git a/app/interfaces/schema.ts b/app/interfaces/schema.ts index 5fb1b8ee..8afe8992 100644 --- a/app/interfaces/schema.ts +++ b/app/interfaces/schema.ts @@ -67,10 +67,10 @@ export interface IAlterForm { } export enum IJobStatus { - queue = 'QUEUE', - running = 'RUNNING', - finished = 'FINISHED', - failed = 'FAILED', - stopped = 'STOPPED', - removed = 'REMOVED', + Queue = 'QUEUE', + Running = 'RUNNING', + Finished = 'FINISHED', + Failed = 'FAILED', + Stopped = 'STOPPED', + Removed = 'REMOVED', } \ No newline at end of file diff --git a/app/pages/Import/TaskList/TaskItem/index.tsx b/app/pages/Import/TaskList/TaskItem/index.tsx index 5c7c78c3..e98bfb0d 100644 --- a/app/pages/Import/TaskList/TaskItem/index.tsx +++ b/app/pages/Import/TaskList/TaskItem/index.tsx @@ -51,9 +51,9 @@ const TaskItem = (props: IProps) => { const [status, setStatus] = useState<'success' | 'active' | 'normal' | 'exception' | undefined>(undefined); const [extraMsg, setExtraMsg] = useState(''); useEffect(() => { - if(taskStatus === ITaskStatus.statusFinished) { + if(taskStatus === ITaskStatus.StatusFinished) { setStatus('success'); - } else if(taskStatus === ITaskStatus.statusProcessing) { + } else if(taskStatus === ITaskStatus.StatusProcessing) { setStatus('active'); const info: string[] = []; if(numFailed > 0) { @@ -84,22 +84,22 @@ const TaskItem = (props: IProps) => {
{name} - {taskStatus === ITaskStatus.statusFinished && + {taskStatus === ITaskStatus.StatusFinished && {intl.get('import.importCompleted')} {extraMsg && ` (${extraMsg})`} } - {taskStatus === ITaskStatus.statusAborted && + {taskStatus === ITaskStatus.StatusAborted && {intl.get('import.importFailed')} {extraMsg && ` (${extraMsg})`} } - {taskStatus === ITaskStatus.statusStoped && + {taskStatus === ITaskStatus.StatusStoped && {intl.get('import.importStopped')} }
- {taskStatus !== ITaskStatus.statusFinished && `${totalCount} ${intl.get('import.lines')} / `} + {taskStatus !== ITaskStatus.StatusFinished && `${totalCount} ${intl.get('import.lines')} / `} {totalLine}{' '}{intl.get('import.lines')} {dayjs.duration(dayjs.unix(updatedTime).diff(dayjs.unix(createdTime))).format('HH:mm:ss')} @@ -107,13 +107,13 @@ const TaskItem = (props: IProps) => {
- {taskStatus === ITaskStatus.statusProcessing && + {taskStatus === ITaskStatus.StatusProcessing && { > } - {taskStatus !== ITaskStatus.statusProcessing && + {taskStatus !== ITaskStatus.StatusProcessing && { }; }, []); useEffect(() => { - const needRefresh = taskList.filter(item => item.taskStatus === ITaskStatus.statusProcessing).length > 0; + const needRefresh = taskList.filter(item => item.taskStatus === ITaskStatus.StatusProcessing).length > 0; if(needRefresh && isMounted) { timer.current = setTimeout(asyncGetTaskList, 2000); } else { diff --git a/app/pages/Schema/SchemaConfig/List/Index/index.tsx b/app/pages/Schema/SchemaConfig/List/Index/index.tsx index 7daaef51..1a10b160 100644 --- a/app/pages/Schema/SchemaConfig/List/Index/index.tsx +++ b/app/pages/Schema/SchemaConfig/List/Index/index.tsx @@ -1,7 +1,7 @@ import { Button, Popconfirm, Radio, Table, message } from 'antd'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import intl from 'react-intl-universal'; -import { Link, useHistory, useParams } from 'react-router-dom'; +import { useHistory, useParams } from 'react-router-dom'; import Icon from '@app/components/Icon'; import { observer } from 'mobx-react-lite'; import { useStore } from '@app/stores'; diff --git a/app/pages/Schema/SchemaConfig/List/SpaceStats/index.less b/app/pages/Schema/SchemaConfig/List/SpaceStats/index.less new file mode 100644 index 00000000..0d0ffa9c --- /dev/null +++ b/app/pages/Schema/SchemaConfig/List/SpaceStats/index.less @@ -0,0 +1,20 @@ +@import '~@app/common.less'; +.nebula-stats { + .operations { + margin: 20px 0 17px; + display: flex; + align-items: center; + font-size: 14px; + .ant-btn { + width: 110px; + } + .label { + margin-left: 30px; + color: @darkGray; + } + .label::after { + content: ": "; + padding-right: 5px; + } + } +} \ No newline at end of file diff --git a/app/pages/Schema/SchemaConfig/List/SpaceStats/index.tsx b/app/pages/Schema/SchemaConfig/List/SpaceStats/index.tsx new file mode 100644 index 00000000..013d3580 --- /dev/null +++ b/app/pages/Schema/SchemaConfig/List/SpaceStats/index.tsx @@ -0,0 +1,125 @@ +import { Button, Table, message } from 'antd'; +import dayjs from 'dayjs'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import intl from 'react-intl-universal'; +import { useParams } from 'react-router-dom'; +import { observer } from 'mobx-react-lite'; +import { useStore } from '@app/stores'; +import { IJobStatus } from '@app/interfaces/schema'; +import { trackPageView } from '@app/utils/stat'; +import Cookie from 'js-cookie'; + +import './index.less'; + +const SpaceStats = () => { + const timer = useRef(null); + const { schema: { getJobStatus, submitStats, getStats } } = useStore(); + const { space } = useParams() as { space :string }; + const [data, setData] = useState([]); + const [updateTime, setUpdateTime] = useState(''); + const [jobId, setJobId] = useState(null); + const [loading, setLoading] = useState(false); + const columns = useMemo(() => [ + { + title: intl.get('schema.statsType'), + dataIndex: 'Type', + }, + { + title: intl.get('schema.statsName'), + dataIndex: 'Name', + }, + { + title: intl.get('schema.statsCount'), + dataIndex: 'Count', + }, + ], [Cookie.get('lang')]); + useEffect(() => { + trackPageView('/space/stats'); + initData(); + getJobs(); + return () => { + timer.current && clearTimeout(timer.current); + }; + }, [space]); + + const initData = () => { + setJobId(null); + setUpdateTime(''); + setData([]); + }; + + const getData = async() => { + const { code, data } = await getStats(); + if (code === 0) { + setData(data.tables); + } + }; + + const getJobs = async() => { + const { code, data } = await getJobStatus(); + if (code === 0) { + const stat = data.tables.filter(item => item.Command === 'STATS')[0]; + if (stat?.Status === IJobStatus.Finished) { + getData(); + setUpdateTime(stat['Stop Time']); + } else if (stat) { + const jobId = stat['Job Id']; + setJobId(jobId); + getStatStatus(jobId); + } + } + }; + + const getStatStatus = async id => { + const { code, data } = await getJobStatus(id); + if (code === 0) { + const job = data.tables[0]; + if (job.Status === IJobStatus.Finished) { + getData(); + setUpdateTime(job['Stop Time']); + setJobId(null); + message.success(intl.get('schema.statFinished')); + } else if ([IJobStatus.Running, IJobStatus.Queue].includes(job.Status)) { + timer.current = setTimeout(() => getStatStatus(id), 2000); + } else if (job.Status === 'FAILED') { + message.warning(intl.get('schema.statError')); + setJobId(null); + } + } + }; + const handleSubmitStats = async() => { + setLoading(true); + const { code, data } = await submitStats(); + setLoading(false); + if (code === 0) { + const id = data.tables[0]['New Job Id']; + setJobId(id); + await getStatStatus(id); + } + }; + return ( +
+
+ + {intl.get('schema.lastRefreshTime')} + + {updateTime ? dayjs(updateTime).format('YYYY-MM-DD HH:mm:ss') : null} + +
+ + + ); +}; + +export default observer(SpaceStats); diff --git a/app/pages/Schema/SchemaConfig/index.tsx b/app/pages/Schema/SchemaConfig/index.tsx index 50da781d..ce96bc93 100644 --- a/app/pages/Schema/SchemaConfig/index.tsx +++ b/app/pages/Schema/SchemaConfig/index.tsx @@ -7,6 +7,7 @@ import Breadcrumb from '@app/components/Breadcrumb'; import TagList from './List/Tag'; import EdgeList from './List/Edge'; import IndexList from './List/Index/index'; +import SpaceStats from './List/SpaceStats'; import CommonCreate from './Create/CommonCreate'; import IndexCreate from './Create/IndexCreate'; import CommonEdit from './Edit/CommonEdit'; @@ -113,6 +114,11 @@ const SchemaConfig = () => { exact={true} component={IndexList} /> + item['Index Status'] !== IJobStatus.finished) + .filter(item => item['Index Status'] !== IJobStatus.Finished) .map(item => item.Name); } return null; } + // stats + submitStats = async() => { + const { code, data } = (await service.execNGQL( + { + gql: ` + SUBMIT JOB STATS + `, + }, + { + trackEventConfig: { + category: 'schema', + action: 'submit_stats', + }, + }, + )) as any; + return { code, data }; + } + + getStats = async() => { + const { code, data } = (await service.execNGQL({ + gql: ` + SHOW STATS + `, + })) as any; + return { code, data }; + } + + getJobStatus = async(id?) => { + const gql = id === undefined ? 'SHOW JOBS' : `SHOW JOB ${id}`; + const { code, data } = (await service.execNGQL({ + gql, + })) as any; + return { code, data }; + } } const schemaStore = new SchemaStore();