Skip to content

Commit 87b2ef2

Browse files
committed
feat: refactor cls module with interface
1 parent 4697aa3 commit 87b2ef2

File tree

2 files changed

+404
-16
lines changed

2 files changed

+404
-16
lines changed

src/modules/cls/dashboard.ts

Lines changed: 388 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
import ReactGridLayout from 'react-grid-layout';
2+
import Cls from '.';
3+
import * as uuid from 'uuid';
4+
import { ApiError } from '../../utils/error';
5+
6+
export interface RemoveDashboardInputs {
7+
name?: string;
8+
id?: string;
9+
}
10+
11+
export enum DashboardChartType {
12+
table = 'table',
13+
graph = 'graph',
14+
bar = 'bar',
15+
stat = 'stat',
16+
gauge = 'gauge',
17+
pie = 'pie',
18+
19+
sankey = 'sankey',
20+
map = 'map',
21+
tMap = 'tMap', // 未启用
22+
23+
row = 'row',
24+
'add-panel' = 'add-panel',
25+
}
26+
27+
export namespace Raw {
28+
export const regionId2Name: Record<number, string> = {
29+
1: 'ap-guangzhou',
30+
4: 'ap-shanghai',
31+
5: 'ap-hongkong',
32+
6: 'na-toronto',
33+
7: 'ap-shanghai-fsi',
34+
8: 'ap-beijing',
35+
9: 'ap-singapore',
36+
11: 'ap-shenzhen-fsi',
37+
12: 'ap-guangzhou-open',
38+
15: 'na-siliconvalley',
39+
16: 'ap-chengdu',
40+
17: 'eu-frankfurt',
41+
18: 'ap-seoul',
42+
19: 'ap-chongqing',
43+
21: 'ap-mumbai',
44+
22: 'na-ashburn',
45+
23: 'ap-bangkok',
46+
24: 'eu-moscow',
47+
25: 'ap-tokyo',
48+
31: 'ap-jinan-ec',
49+
32: 'ap-hangzhou-ec',
50+
33: 'ap-nanjing',
51+
34: 'ap-fuzhou-ec',
52+
35: 'ap-wuhan-ec',
53+
36: 'ap-tianjin',
54+
37: 'ap-shenzhen',
55+
39: 'ap-taipei',
56+
45: 'ap-changsha-ec',
57+
46: 'ap-beijing-fsi',
58+
53: 'ap-shijiazhuang-ec',
59+
54: 'ap-qingyuan',
60+
55: 'ap-hefei-ec',
61+
56: 'ap-shenyang-ec',
62+
57: 'ap-xian-ec',
63+
58: 'ap-xibei-ec',
64+
71: 'ap-zhengzhou-ec',
65+
72: 'ap-jakarta',
66+
73: 'ap-qingyuan-xinan',
67+
74: 'sa-saopaulo',
68+
};
69+
export const regionName2Id: Record<string, number> = {
70+
'ap-guangzhou': 1,
71+
'ap-shanghai': 4,
72+
'ap-hongkong': 5,
73+
'na-toronto': 6,
74+
'ap-shanghai-fsi': 7,
75+
'ap-beijing': 8,
76+
'ap-singapore': 9,
77+
'ap-shenzhen-fsi': 11,
78+
'ap-guangzhou-open': 12,
79+
'na-siliconvalley': 15,
80+
'ap-chengdu': 16,
81+
'eu-frankfurt': 17,
82+
'ap-seoul': 18,
83+
'ap-chongqing': 19,
84+
'ap-mumbai': 21,
85+
'na-ashburn': 22,
86+
'ap-bangkok': 23,
87+
'eu-moscow': 24,
88+
'ap-tokyo': 25,
89+
'ap-jinan-ec': 31,
90+
'ap-hangzhou-ec': 32,
91+
'ap-nanjing': 33,
92+
'ap-fuzhou-ec': 34,
93+
'ap-wuhan-ec': 35,
94+
'ap-tianjin': 36,
95+
'ap-shenzhen': 37,
96+
'ap-taipei': 39,
97+
'ap-changsha-ec': 45,
98+
'ap-beijing-fsi': 46,
99+
'ap-shijiazhuang-ec': 53,
100+
'ap-qingyuan': 54,
101+
'ap-hefei-ec': 55,
102+
'ap-shenyang-ec': 56,
103+
'ap-xian-ec': 57,
104+
'ap-xibei-ec': 58,
105+
'ap-zhengzhou-ec': 71,
106+
'ap-jakarta': 72,
107+
'ap-qingyuan-xinan': 73,
108+
'sa-saopaulo': 74,
109+
};
110+
111+
export interface DashboardChartTarget {
112+
RegionId: number;
113+
LogsetId: string;
114+
TopicId: string;
115+
Query: string;
116+
/** 图表数据处理参数 */
117+
ChartAxis?: {
118+
xAxisKey?: string;
119+
yAxisKey?: string;
120+
aggregateKey?: string;
121+
};
122+
}
123+
124+
type FieldConfigSource = unknown;
125+
126+
export interface DashboardChart {
127+
id: string;
128+
title: string;
129+
description?: string;
130+
gridPos: Partial<ReactGridLayout.Layout>;
131+
/** 图表类型 */
132+
type: DashboardChartType;
133+
/** 数据请求涉及参数 */
134+
target: DashboardChartTarget;
135+
/**
136+
* 图表配置,和图表的类型有关,每个图表类型都有独立的配置
137+
*/
138+
options?: unknown;
139+
140+
chartConfig?: unknown;
141+
/**
142+
* filed配置,包含默认配置和针对某个filed的override情况,对数值的处理、特殊显示、link、mappings都属于此类
143+
* 和具体的图表类型无关,配置修改的是dataFrame本身
144+
*/
145+
fieldConfig?: FieldConfigSource;
146+
}
147+
148+
// 云 API 返回的 dashboard 结构
149+
export interface Dashboard {
150+
CreateTime: string;
151+
DashboardId: string;
152+
DashboardName: string;
153+
data: string;
154+
}
155+
}
156+
157+
export interface LogsetConfig {
158+
region: string;
159+
logsetId: string;
160+
topicId: string;
161+
}
162+
163+
export interface DashboardChart {
164+
id: string;
165+
title: string;
166+
description?: string;
167+
type: DashboardChartType;
168+
query: string;
169+
170+
xAxisKey?: string;
171+
yAxisKey?: string;
172+
173+
aggregateKey?: string;
174+
}
175+
176+
export type DeployDashChartInputs = Omit<DashboardChart, 'id'>;
177+
178+
export interface DeployDashboardInputs {
179+
name: string;
180+
chartList: DeployDashChartInputs[];
181+
}
182+
183+
// camelCase 的 dashboard 结构,并作了简化
184+
export interface Dashboard {
185+
createTime: string;
186+
id: string;
187+
name: string;
188+
chartList: DashboardChart[];
189+
}
190+
191+
export class ClsDashboard {
192+
cls: Cls;
193+
constructor(cls: Cls) {
194+
this.cls = cls;
195+
}
196+
197+
// 获取 dashboard 列表
198+
async getDashboardList(): Promise<Dashboard[]> {
199+
const res = await this.cls.clsClient.request({
200+
method: 'GET',
201+
path: '/dashboards',
202+
});
203+
if (res.error) {
204+
throw new ApiError({
205+
type: 'API_getDashboard',
206+
message: res.error.message,
207+
});
208+
}
209+
const dashboards = ((res.dashboards || []) as Raw.Dashboard[]).map(
210+
({ CreateTime, DashboardName, DashboardId, data }: Raw.Dashboard) => {
211+
const parseData = JSON.parse(data);
212+
const dashboard: Dashboard = {
213+
createTime: CreateTime,
214+
name: DashboardName,
215+
id: DashboardId,
216+
chartList: parseData.panels,
217+
};
218+
219+
return dashboard;
220+
},
221+
);
222+
223+
return dashboards;
224+
}
225+
226+
// 获取 dashboard 详情
227+
async getDashboardDetail({
228+
name,
229+
id,
230+
}: {
231+
name?: string;
232+
id?: string;
233+
}): Promise<Dashboard | undefined> {
234+
if (id) {
235+
const res = await this.cls.clsClient.request({
236+
method: 'GET',
237+
path: `/dashboard`,
238+
query: {
239+
DashboardId: id,
240+
},
241+
});
242+
if (res.error) {
243+
return undefined;
244+
}
245+
246+
const parseData = JSON.parse(res.data);
247+
const rawPanels: Raw.DashboardChart[] = parseData.panels;
248+
249+
return {
250+
id,
251+
createTime: res.CreateTime,
252+
name: res.DashboardName,
253+
chartList: rawPanels.map((v) => ({
254+
id: v.id,
255+
title: v.title,
256+
description: v.description,
257+
type: v.type,
258+
query: v.target.Query,
259+
xAxisKey: v.target.ChartAxis?.xAxisKey,
260+
yAxisKey: v.target.ChartAxis?.yAxisKey,
261+
aggregateKey: v.target.ChartAxis?.aggregateKey,
262+
})),
263+
};
264+
}
265+
if (name) {
266+
const list = await this.getDashboardList();
267+
const exist = list.find((item) => item.name === name);
268+
if (exist) {
269+
return exist;
270+
}
271+
return undefined;
272+
}
273+
throw new ApiError({
274+
type: 'API_getDashboardDetail',
275+
message: 'name or id is required',
276+
});
277+
}
278+
279+
// 删除 dashboard
280+
async removeDashboard({ id, name }: RemoveDashboardInputs) {
281+
if (!id && !name) {
282+
throw new ApiError({
283+
type: 'API_removeDashboard',
284+
message: 'id or name is required',
285+
});
286+
}
287+
if (!id) {
288+
// 通过名称查找ID
289+
const exist = await this.getDashboardDetail({ name });
290+
if (!exist) {
291+
console.log(`Dashboard ${name} not exist`);
292+
293+
return true;
294+
}
295+
({ id } = exist);
296+
}
297+
// 删除 dashboard
298+
const res = await this.cls.clsClient.request({
299+
method: 'DELETE',
300+
path: `/dashboard`,
301+
query: {
302+
DashboardId: id,
303+
},
304+
});
305+
306+
if (res.error) {
307+
throw new ApiError({
308+
type: 'API_deleteDashboard',
309+
message: res.error.message,
310+
});
311+
}
312+
313+
return true;
314+
}
315+
316+
// 创建 dashboard
317+
async deployDashboard(inputs: DeployDashboardInputs, logsetConfig: LogsetConfig) {
318+
const { name, chartList } = inputs;
319+
const data = JSON.stringify({
320+
panels: chartList.map((v) => {
321+
const panel: Raw.DashboardChart = {
322+
id: 'chart-' + uuid.v4(),
323+
title: v.title,
324+
gridPos: { x: 0, y: 0, w: 12, h: 8 },
325+
description: v.description,
326+
type: v.type,
327+
target: {
328+
RegionId: Raw.regionName2Id[logsetConfig.region],
329+
LogsetId: logsetConfig.logsetId,
330+
TopicId: logsetConfig.topicId,
331+
Query: v.query,
332+
ChartAxis: {
333+
xAxisKey: v.xAxisKey,
334+
yAxisKey: v.yAxisKey,
335+
aggregateKey: v.aggregateKey,
336+
},
337+
},
338+
};
339+
return panel;
340+
}),
341+
});
342+
343+
// 1. 检查是否存在同名 dashboard
344+
const exist = await this.getDashboardDetail({ name });
345+
let dashboardId = '';
346+
// 2. 如果不存在则创建,否则更新
347+
if (exist) {
348+
dashboardId = exist.id;
349+
const res = await this.cls.clsClient.request({
350+
method: 'PUT',
351+
path: '/dashboard',
352+
data: {
353+
DashboardId: exist.id,
354+
DashboardName: name,
355+
data,
356+
},
357+
});
358+
if (res.error) {
359+
throw new ApiError({
360+
type: 'API_updateDashboard',
361+
message: res.error.message,
362+
});
363+
}
364+
} else {
365+
const res = await this.cls.clsClient.request({
366+
method: 'POST',
367+
path: '/dashboard',
368+
data: {
369+
DashboardName: name,
370+
data,
371+
},
372+
});
373+
if (res.error) {
374+
throw new ApiError({
375+
type: 'API_createDashboard',
376+
message: res.error.message,
377+
});
378+
}
379+
dashboardId = res.DashboardId;
380+
}
381+
382+
return {
383+
id: dashboardId,
384+
name,
385+
outputs: inputs,
386+
};
387+
}
388+
}

0 commit comments

Comments
 (0)