Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add stats #132

Merged
merged 1 commit into from
Feb 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only exact is enough, this is optional

<Route exact />

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