Skip to content

Commit

Permalink
feat: add stats (vesoft-inc#132)
Browse files Browse the repository at this point in the history
mod: code review
  • Loading branch information
hetao92 committed Mar 31, 2022
1 parent 98e610e commit 9145bd7
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 25 deletions.
9 changes: 8 additions & 1 deletion app/config/locale/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,14 @@
"geography(linestring)Format": "Supported data inserting methods: <br /> Call function ST_GeogFromText('LINESTRING()'), for example:ST_GeogFromText('LINESTRING(3 4,10 50,20 25)')",
"geography(polygon)Format": "Supported data inserting methods: <br /> 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: <br /> Call function duration(<map>), 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",
Expand Down
9 changes: 8 additions & 1 deletion app/config/locale/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,14 @@
"geography(linestring)Format": "geo(linestring) 类型支持插入方式: <br /> 调用函数 ST_GeogFromText('LINESTRING()'),例如:ST_GeogFromText('LINESTRING(3 4,10 50,20 25)')",
"geography(polygon)Format": "geo(polygon) 类型支持插入方式: <br /> 调用函数 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 类型支持插入方式: <br /> 调用函数 duration(<map>),例如:duration({years: 1, seconds: 0})",
"setTTL": "设置TTL(存活时间)"
"setTTL": "设置TTL(存活时间)",
"refresh": "更新",
"lastRefreshTime": "上次更新时间",
"statsType": "维度",
"statsName": "名称",
"statsCount": "数量",
"statError": "统计失败,请重试",
"statFinished": "统计结束"
},
"menu": {
"use": "使用手册",
Expand Down
10 changes: 5 additions & 5 deletions app/interfaces/import.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
12 changes: 6 additions & 6 deletions app/interfaces/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}
18 changes: 9 additions & 9 deletions app/pages/Import/TaskList/TaskItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -84,36 +84,36 @@ const TaskItem = (props: IProps) => {
<div className="progress-info">
<span className="task-name">
{name}
{taskStatus === ITaskStatus.statusFinished && <span className="complete-info">
{taskStatus === ITaskStatus.StatusFinished && <span className="complete-info">
<CheckCircleFilled />
{intl.get('import.importCompleted')}
{extraMsg && ` (${extraMsg})`}
</span>}
{taskStatus === ITaskStatus.statusAborted && <span className="error-info">
{taskStatus === ITaskStatus.StatusAborted && <span className="error-info">
{intl.get('import.importFailed')}
{extraMsg && ` (${extraMsg})`}
</span>}
{taskStatus === ITaskStatus.statusStoped && <span className="error-info">
{taskStatus === ITaskStatus.StatusStoped && <span className="error-info">
{intl.get('import.importStopped')}
</span>}
</span>
<div className="more-info">
<span>
{taskStatus !== ITaskStatus.statusFinished && `${totalCount} ${intl.get('import.lines')} / `}
{taskStatus !== ITaskStatus.StatusFinished && `${totalCount} ${intl.get('import.lines')} / `}
{totalLine}{' '}{intl.get('import.lines')}
</span>
<span>{dayjs.duration(dayjs.unix(updatedTime).diff(dayjs.unix(createdTime))).format('HH:mm:ss')}</span>
</div>
</div>
<Progress
status={status}
percent={taskStatus !== ITaskStatus.statusFinished ? floor(totalCount / totalLine * 100, 2) : 100}
percent={taskStatus !== ITaskStatus.StatusFinished ? floor(totalCount / totalLine * 100, 2) : 100}
strokeColor={status && COLOR_MAP[status]} />
</div>
<div className="operations">
<Button className="primary-btn">{intl.get('import.details')}</Button>
<Button className="primary-btn">{intl.get('import.viewLogs')}</Button>
{taskStatus === ITaskStatus.statusProcessing &&
{taskStatus === ITaskStatus.StatusProcessing &&
<Popconfirm
placement="left"
title={intl.get('import.endImport')}
Expand All @@ -123,7 +123,7 @@ const TaskItem = (props: IProps) => {
>
<Button className="cancel-btn">{intl.get('import.endImport')}</Button>
</Popconfirm>}
{taskStatus !== ITaskStatus.statusProcessing &&
{taskStatus !== ITaskStatus.StatusProcessing &&
<Popconfirm
placement="left"
title={intl.get('common.ask')}
Expand Down
2 changes: 1 addition & 1 deletion app/pages/Import/TaskList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const TaskList = () => {
};
}, []);
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 {
Expand Down
2 changes: 1 addition & 1 deletion app/pages/Schema/SchemaConfig/List/Index/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
20 changes: 20 additions & 0 deletions app/pages/Schema/SchemaConfig/List/SpaceStats/index.less
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
125 changes: 125 additions & 0 deletions app/pages/Schema/SchemaConfig/List/SpaceStats/index.tsx
Original file line number Diff line number Diff line change
@@ -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<NodeJS.Timeout | null>(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<any>(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 (
<div className="nebula-stats">
<div className="operations">
<Button
type="primary"
onClick={handleSubmitStats}
loading={loading || jobId !== null}
>
{intl.get('schema.refresh')}
</Button>
<span className="label">{intl.get('schema.lastRefreshTime')}</span>
<span>
{updateTime ? dayjs(updateTime).format('YYYY-MM-DD HH:mm:ss') : null}
</span>
</div>
<Table
className="expanded-table"
dataSource={data}
columns={columns}
rowKey="Name"
/>
</div>
);
};

export default observer(SpaceStats);
6 changes: 6 additions & 0 deletions app/pages/Schema/SchemaConfig/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -113,6 +114,11 @@ const SchemaConfig = () => {
exact={true}
component={IndexList}
/>
<Route
path="/schema/:space/statistic/list"
exact={true}
component={SpaceStats}
/>
<Route
path={`/schema/:space/tag/create`}
exact={true}
Expand Down
36 changes: 35 additions & 1 deletion app/stores/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,12 +571,46 @@ export class SchemaStore {
})) as any;
if (code === 0) {
return data.tables
.filter(item => 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();
Expand Down

0 comments on commit 9145bd7

Please sign in to comment.