balibabu commited on
Commit
f36a767
·
1 Parent(s): 9640d9a

feat: Show task_executor heartbeat #3409 (#3461)

Browse files

### What problem does this PR solve?

feat: Show task_executor heartbeat #3409
### Type of change


- [x] New Feature (non-breaking change which adds functionality)

web/src/interfaces/database/user-setting.ts CHANGED
@@ -22,16 +22,23 @@ export interface IUserInfo {
22
 
23
  export type TaskExecutorElapsed = Record<string, number[]>;
24
 
 
 
 
 
 
 
 
 
 
 
 
25
  export interface ISystemStatus {
26
  es: Es;
27
  storage: Storage;
28
  database: Database;
29
  redis: Redis;
30
- task_executor: {
31
- error?: string;
32
- status: string;
33
- elapsed?: TaskExecutorElapsed;
34
- };
35
  }
36
 
37
  interface Redis {
 
22
 
23
  export type TaskExecutorElapsed = Record<string, number[]>;
24
 
25
+ export interface TaskExecutorHeartbeatItem {
26
+ boot_at: string;
27
+ current: null;
28
+ done: number;
29
+ failed: number;
30
+ lag: number;
31
+ name: string;
32
+ now: string;
33
+ pending: number;
34
+ }
35
+
36
  export interface ISystemStatus {
37
  es: Es;
38
  storage: Storage;
39
  database: Database;
40
  redis: Redis;
41
+ task_executor_heartbeat: Record<string, TaskExecutorHeartbeatItem[]>;
 
 
 
 
42
  }
43
 
44
  interface Redis {
web/src/pages/user-setting/setting-system/index.tsx CHANGED
@@ -2,7 +2,7 @@ import SvgIcon from '@/components/svg-icon';
2
  import { useFetchSystemStatus } from '@/hooks/user-setting-hooks';
3
  import {
4
  ISystemStatus,
5
- TaskExecutorElapsed,
6
  } from '@/interfaces/database/user-setting';
7
  import { Badge, Card, Flex, Spin, Typography } from 'antd';
8
  import classNames from 'classnames';
@@ -11,6 +11,7 @@ import upperFirst from 'lodash/upperFirst';
11
  import { useEffect } from 'react';
12
 
13
  import { toFixed } from '@/utils/common-util';
 
14
  import styles from './index.less';
15
  import TaskBarChat from './task-bar-chat';
16
 
@@ -27,7 +28,7 @@ const TitleMap = {
27
  storage: 'Object Storage',
28
  redis: 'Redis',
29
  database: 'Database',
30
- task_executor: 'Task Executor',
31
  };
32
 
33
  const IconMap = {
@@ -60,10 +61,13 @@ const SystemInfo = () => {
60
  type="inner"
61
  title={
62
  <Flex align="center" gap={10}>
63
- {key === 'task_executor' ? (
64
  <img src="/logo.svg" alt="" width={26} />
65
  ) : (
66
- <SvgIcon name={IconMap[key as keyof typeof IconMap]} width={26}></SvgIcon>
 
 
 
67
  )}
68
  <span className={styles.title}>
69
  {TitleMap[key as keyof typeof TitleMap]}
@@ -76,13 +80,15 @@ const SystemInfo = () => {
76
  }
77
  key={key}
78
  >
79
- {key === 'task_executor' ? (
80
- info?.elapsed ? (
81
  <TaskBarChat
82
- data={info.elapsed as TaskExecutorElapsed}
83
  ></TaskBarChat>
84
  ) : (
85
- <Text className={styles.error}>{info.error}</Text>
 
 
86
  )
87
  ) : (
88
  Object.keys(info)
 
2
  import { useFetchSystemStatus } from '@/hooks/user-setting-hooks';
3
  import {
4
  ISystemStatus,
5
+ TaskExecutorHeartbeatItem,
6
  } from '@/interfaces/database/user-setting';
7
  import { Badge, Card, Flex, Spin, Typography } from 'antd';
8
  import classNames from 'classnames';
 
11
  import { useEffect } from 'react';
12
 
13
  import { toFixed } from '@/utils/common-util';
14
+ import { isObject } from 'lodash';
15
  import styles from './index.less';
16
  import TaskBarChat from './task-bar-chat';
17
 
 
28
  storage: 'Object Storage',
29
  redis: 'Redis',
30
  database: 'Database',
31
+ task_executor_heartbeats: 'Task Executor',
32
  };
33
 
34
  const IconMap = {
 
61
  type="inner"
62
  title={
63
  <Flex align="center" gap={10}>
64
+ {key === 'task_executor_heartbeats' ? (
65
  <img src="/logo.svg" alt="" width={26} />
66
  ) : (
67
+ <SvgIcon
68
+ name={IconMap[key as keyof typeof IconMap]}
69
+ width={26}
70
+ ></SvgIcon>
71
  )}
72
  <span className={styles.title}>
73
  {TitleMap[key as keyof typeof TitleMap]}
 
80
  }
81
  key={key}
82
  >
83
+ {key === 'task_executor_heartbeats' ? (
84
+ isObject(info) ? (
85
  <TaskBarChat
86
+ data={info as Record<string, TaskExecutorHeartbeatItem[]>}
87
  ></TaskBarChat>
88
  ) : (
89
+ <Text className={styles.error}>
90
+ {typeof info.error === 'string' ? info.error : ''}
91
+ </Text>
92
  )
93
  ) : (
94
  Object.keys(info)
web/src/pages/user-setting/setting-system/task-bar-chat.tsx CHANGED
@@ -1,57 +1,47 @@
1
- import { TaskExecutorElapsed } from '@/interfaces/database/user-setting';
2
  import { Divider, Flex } from 'antd';
3
- import { max } from 'lodash';
4
  import {
5
  Bar,
6
  BarChart,
7
  CartesianGrid,
 
 
8
  ResponsiveContainer,
9
  Tooltip,
 
10
  } from 'recharts';
11
 
 
 
 
12
  import styles from './index.less';
13
 
14
  interface IProps {
15
- data: TaskExecutorElapsed;
16
  }
17
 
18
- const getColor = (value: number) => {
19
- if (value > 120) {
20
- return 'red';
21
- } else if (value <= 120 && value > 50) {
22
- return '#faad14';
23
- }
24
- return '#52c41a';
25
- };
26
-
27
- const getMaxLength = (data: TaskExecutorElapsed) => {
28
- const lengths = Object.keys(data).reduce<number[]>((pre, cur) => {
29
- pre.push(data[cur].length);
30
- return pre;
31
- }, []);
32
- return max(lengths) ?? 0;
33
- };
34
-
35
- const fillEmptyElementByMaxLength = (list: any[], maxLength: number) => {
36
- if (list.length === maxLength) {
37
- return list;
38
- }
39
- return list.concat(
40
- new Array(maxLength - list.length).fill({
41
- value: 0,
42
- actualValue: 0,
43
- fill: getColor(0),
44
- }),
45
- );
46
- };
47
-
48
- const CustomTooltip = ({ active, payload }: any) => {
49
  if (active && payload && payload.length) {
 
 
 
 
 
50
  return (
51
- <div className="custom-tooltip">
52
- <p
53
- className={styles.taskBarTooltip}
54
- >{`${payload[0].payload.actualValue}`}</p>
 
 
 
 
 
 
 
 
 
 
55
  </div>
56
  );
57
  }
@@ -60,32 +50,56 @@ const CustomTooltip = ({ active, payload }: any) => {
60
  };
61
 
62
  const TaskBarChat = ({ data }: IProps) => {
63
- const maxLength = getMaxLength(data);
64
- return (
65
- <Flex gap="middle" vertical>
66
- {Object.keys(data).map((key) => {
67
- const list = data[key].map((x) => ({
68
- value: x > 120 ? 120 : x,
69
- actualValue: x,
70
- fill: getColor(x),
71
- }));
72
- const nextList = fillEmptyElementByMaxLength(list, maxLength);
73
- return (
74
- <Flex key={key} className={styles.taskBar} vertical>
75
- <b className={styles.taskBarTitle}>ID: {key}</b>
76
- <ResponsiveContainer>
77
- <BarChart data={nextList} barSize={20}>
78
- <CartesianGrid strokeDasharray="3 3" />
79
- <Tooltip content={<CustomTooltip></CustomTooltip>} />
80
- <Bar dataKey="value" />
81
- </BarChart>
82
- </ResponsiveContainer>
83
- <Divider></Divider>
84
- </Flex>
85
- );
86
- })}
87
- </Flex>
88
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  };
90
 
91
  export default TaskBarChat;
 
1
+ import { TaskExecutorHeartbeatItem } from '@/interfaces/database/user-setting';
2
  import { Divider, Flex } from 'antd';
 
3
  import {
4
  Bar,
5
  BarChart,
6
  CartesianGrid,
7
+ Legend,
8
+ Rectangle,
9
  ResponsiveContainer,
10
  Tooltip,
11
+ XAxis,
12
  } from 'recharts';
13
 
14
+ import { formatDate, formatTime } from '@/utils/date';
15
+ import dayjs from 'dayjs';
16
+ import { get } from 'lodash';
17
  import styles from './index.less';
18
 
19
  interface IProps {
20
+ data: Record<string, TaskExecutorHeartbeatItem[]>;
21
  }
22
 
23
+ const CustomTooltip = ({ active, payload, ...restProps }: any) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  if (active && payload && payload.length) {
25
+ const taskExecutorHeartbeatItem: TaskExecutorHeartbeatItem = get(
26
+ payload,
27
+ '0.payload',
28
+ {},
29
+ );
30
  return (
31
+ <div className="bg-slate-50 p-2 rounded-md border border-indigo-100">
32
+ <div className="font-semibold text-lg">
33
+ {formatDate(restProps.label)}
34
+ </div>
35
+ {Object.entries(taskExecutorHeartbeatItem).map(([key, val], index) => {
36
+ return (
37
+ <div key={index} className="flex gap-1">
38
+ <span className="font-semibold">{`${key}: `}</span>
39
+ <span>
40
+ {key === 'now' || key === 'boot_at' ? formatDate(val) : val}
41
+ </span>
42
+ </div>
43
+ );
44
+ })}
45
  </div>
46
  );
47
  }
 
50
  };
51
 
52
  const TaskBarChat = ({ data }: IProps) => {
53
+ return Object.entries(data).map(([key, val]) => {
54
+ const data = val.map((x) => ({
55
+ ...x,
56
+ now: dayjs(x.now).valueOf(),
57
+ failed: 5,
58
+ }));
59
+ const firstItem = data[0];
60
+ const lastItem = data[data.length - 1];
61
+
62
+ const domain = [firstItem.now, lastItem.now];
63
+
64
+ return (
65
+ <Flex key={key} className={styles.taskBar} vertical>
66
+ <div className="flex gap-8">
67
+ <b className={styles.taskBarTitle}>ID: {key}</b>
68
+ <b className={styles.taskBarTitle}>Lag: {lastItem.lag}</b>
69
+ <b className={styles.taskBarTitle}>Pending: {lastItem.pending}</b>
70
+ </div>
71
+ <ResponsiveContainer>
72
+ <BarChart data={data} barSize={20}>
73
+ <XAxis
74
+ dataKey="now"
75
+ type="number"
76
+ scale={'time'}
77
+ domain={domain}
78
+ tickFormatter={(x) => formatTime(x)}
79
+ allowDataOverflow
80
+ angle={60}
81
+ padding={{ left: 20, right: 20 }}
82
+ tickMargin={20}
83
+ />
84
+ <CartesianGrid strokeDasharray="3 3" />
85
+ <Tooltip content={<CustomTooltip></CustomTooltip>} />
86
+ <Legend wrapperStyle={{ bottom: -22 }} />
87
+ <Bar
88
+ dataKey="done"
89
+ fill="#8884d8"
90
+ activeBar={<Rectangle fill="pink" stroke="blue" />}
91
+ />
92
+ <Bar
93
+ dataKey="failed"
94
+ fill="#82ca9d"
95
+ activeBar={<Rectangle fill="gold" stroke="purple" />}
96
+ />
97
+ </BarChart>
98
+ </ResponsiveContainer>
99
+ <Divider></Divider>
100
+ </Flex>
101
+ );
102
+ });
103
  };
104
 
105
  export default TaskBarChat;
web/src/utils/date.ts CHANGED
@@ -1,5 +1,19 @@
1
  import dayjs from 'dayjs';
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  export function today() {
4
  return formatDate(dayjs());
5
  }
@@ -11,10 +25,3 @@ export function lastDay() {
11
  export function lastWeek() {
12
  return formatDate(dayjs().subtract(1, 'weeks'));
13
  }
14
-
15
- export function formatDate(date: any) {
16
- if (!date) {
17
- return '';
18
- }
19
- return dayjs(date).format('DD/MM/YYYY HH:mm:ss');
20
- }
 
1
  import dayjs from 'dayjs';
2
 
3
+ export function formatDate(date: any) {
4
+ if (!date) {
5
+ return '';
6
+ }
7
+ return dayjs(date).format('DD/MM/YYYY HH:mm:ss');
8
+ }
9
+
10
+ export function formatTime(date: any) {
11
+ if (!date) {
12
+ return '';
13
+ }
14
+ return dayjs(date).format('HH:mm:ss');
15
+ }
16
+
17
  export function today() {
18
  return formatDate(dayjs());
19
  }
 
25
  export function lastWeek() {
26
  return formatDate(dayjs().subtract(1, 'weeks'));
27
  }