Skip to content

Commit aa296ee

Browse files
Make ui filters generic
Signed-off-by: Mohamed Abokammer <[email protected]>
1 parent 7fc1455 commit aa296ee

File tree

11 files changed

+4029
-3779
lines changed

11 files changed

+4029
-3779
lines changed

cmds/admin_server/server/descripe.go

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package server
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"strings"
7+
)
8+
9+
const (
10+
INT = "int"
11+
UINT = "uint"
12+
STRING = "string"
13+
TIME = "time"
14+
ENUM = "enum"
15+
)
16+
17+
type FieldMetaData struct {
18+
Name string `json:"name"`
19+
Type string `json:"type"`
20+
Values []string `json:"values"`
21+
}
22+
23+
type Description []FieldMetaData
24+
25+
/*
26+
DescribeEntity returns a Description for some entity fields
27+
to help frontend renders appropiate filters and tables.
28+
it depends on the tags assigned to the entity fields to create the needed metadata.
29+
it expects tags: filter, values
30+
*/
31+
func DescribeEntity(entity interface{}) (Description, error) {
32+
var describtion Description
33+
t := reflect.TypeOf(entity)
34+
35+
for i := 0; i < t.NumField(); i++ {
36+
sf := t.Field(i)
37+
if sf.Anonymous {
38+
return nil, fmt.Errorf("Error Anonymous Field")
39+
}
40+
41+
name := sf.Tag.Get("json")
42+
tagValue := sf.Tag.Get("filter")
43+
var values []string
44+
if tagValue == ENUM {
45+
values = strings.Split(sf.Tag.Get("values"), ",")
46+
}
47+
describtion = append(describtion, FieldMetaData{
48+
Name: name,
49+
Type: tagValue,
50+
Values: values,
51+
})
52+
}
53+
54+
return describtion, nil
55+
}

cmds/admin_server/server/server.go

+24-6
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,15 @@ var (
2222
DefaultDBAccessTimeout time.Duration = 10 * time.Second
2323
)
2424

25+
/*
26+
Query should have the same names as the fields in the entity that it queries(in this case Log).
27+
To generalize filtering time fields (range filtering), it should have two fields with prefixes `start_<name>`, `end_<name>`
28+
to make the frontend generate the query programmatically.
29+
e.g. (Log.Date `date` -> Query.StartDate `start_date`, Query.EndDate `end_date`)
30+
*/
2531
type Query struct {
2632
JobID *uint64 `form:"job_id"`
27-
Text *string `form:"text"`
33+
LogData *string `form:"log_data"`
2834
LogLevel *string `form:"log_level"`
2935
StartDate *time.Time `form:"start_date" time_format:"2006-01-02T15:04:05.000Z07:00"`
3036
EndDate *time.Time `form:"end_date" time_format:"2006-01-02T15:04:05.000Z07:00"`
@@ -40,7 +46,7 @@ func (q *Query) ToStorageQuery() storage.Query {
4046
}
4147

4248
storageQuery.JobID = q.JobID
43-
storageQuery.Text = q.Text
49+
storageQuery.LogData = q.LogData
4450
storageQuery.LogLevel = q.LogLevel
4551
storageQuery.StartDate = q.StartDate
4652
storageQuery.EndDate = q.EndDate
@@ -57,10 +63,10 @@ func (q *Query) ToStorageQuery() storage.Query {
5763
}
5864

5965
type Log struct {
60-
JobID uint64 `json:"job_id"`
61-
LogData string `json:"log_data"`
62-
Date time.Time `json:"date"`
63-
LogLevel string `json:"log_level"`
66+
JobID uint64 `json:"job_id" filter:"uint"`
67+
LogData string `json:"log_data" filter:"string"`
68+
Date time.Time `json:"date" filter:"time"`
69+
LogLevel string `json:"log_level" filter:"enum" values:"info,debug,error,fatal,panic,warning"`
6470
}
6571

6672
func (l *Log) ToStorageLog() storage.Log {
@@ -159,6 +165,17 @@ func (r *RouteHandler) status(c *gin.Context) {
159165
c.JSON(http.StatusOK, gin.H{"status": "live"})
160166
}
161167

168+
//
169+
func (r *RouteHandler) describeLog(c *gin.Context) {
170+
res, err := DescribeEntity(Log{})
171+
if err != nil {
172+
c.JSON(http.StatusInternalServerError, gin.H{"status": "err", "msg": "error while getting the storage descirbtion"})
173+
return
174+
}
175+
176+
c.JSON(http.StatusOK, res)
177+
}
178+
162179
// addLogs inserts log's batches into the database
163180
func (r *RouteHandler) addLogs(c *gin.Context) {
164181
var logs []Log
@@ -277,6 +294,7 @@ func initRouter(ctx xcontext.Context, rh RouteHandler, middlewares []gin.Handler
277294
r.GET("/log", rh.getLogs)
278295
r.GET("/tag", rh.getTags)
279296
r.GET("/tag/:name/jobs", rh.getJobs)
297+
r.GET("/log-description", rh.describeLog)
280298

281299
// serve the frontend app
282300
r.StaticFS("/app", FS(false))

cmds/admin_server/server/static.go

+3,725-3,635
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmds/admin_server/storage/mongo/mongo.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ func toMongoQuery(query storage.Query) bson.D {
6262
})
6363
}
6464

65-
if query.Text != nil {
65+
if query.LogData != nil {
6666
q = append(q, bson.E{
6767
Key: "log_data",
6868
Value: bson.M{
69-
"$regex": primitive.Regex{Pattern: *query.Text, Options: "ig"},
69+
"$regex": primitive.Regex{Pattern: *query.LogData, Options: "ig"},
7070
},
7171
})
7272
}

cmds/admin_server/storage/storage.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type Log struct {
3434
// Query defines the different options to filter with
3535
type Query struct {
3636
JobID *uint64
37-
Text *string
37+
LogData *string
3838
LogLevel *string
3939
StartDate *time.Time
4040
EndDate *time.Time

cmds/admin_server/ui/src/api/logs.ts

+22-25
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,36 @@
11
import superagent from 'superagent';
22

3-
// TODO: remove the hardcoded levels
4-
// logLevels is the possible levels for logs
5-
export const Levels = ['panic', 'fatal', 'error', 'warning', 'info', 'debug'];
6-
7-
// Log defines the expected log entry returned from the api
8-
export interface Log {
9-
job_id: number;
10-
log_data: string;
11-
log_level: string;
12-
date: string;
13-
}
14-
15-
// Query defines all the possible filters to form a query
16-
export interface Query {
17-
job_id?: number;
18-
text?: string;
19-
log_level?: string;
20-
start_date?: string;
21-
end_date?: string;
22-
page: number;
23-
page_size: number;
24-
}
25-
263
// Result defines the structure of the api response to a query
274
export interface Result {
28-
logs: Log[] | null;
5+
logs: any;
296
count: number;
307
page: number;
318
page_size: number;
329
}
3310

3411
// getLogs returns Result that contains logs fetched according to the Query
35-
export async function getLogs(query: Query): Promise<Result> {
12+
export async function getLogs(query: any): Promise<Result> {
3613
let result: superagent.Response = await superagent.get('/log').query(query);
3714

3815
return result.body;
3916
}
17+
18+
export enum FieldType {
19+
INT = 'int',
20+
UINT = 'uint',
21+
STRING = 'string',
22+
TIME = 'time',
23+
ENUM = 'enum',
24+
}
25+
26+
export interface FieldMetaData {
27+
name: string;
28+
type: string;
29+
values: string[];
30+
}
31+
32+
export async function getLogDescription(): Promise<FieldMetaData[]> {
33+
let result: superagent.Response = await superagent.get('/log-description');
34+
35+
return result.body;
36+
}

cmds/admin_server/ui/src/search_logs/log_table/log_table.tsx

+24-38
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,37 @@
1-
import React, { useState } from 'react';
1+
import React, { useMemo, useState } from 'react';
22
import { Table, Pagination, Button, useToaster, Message } from 'rsuite';
3-
import { Column, Cell, HeaderCell } from 'rsuite-table';
43
import { StandardProps } from 'rsuite-table/lib/@types/common';
5-
import { getLogs, Log, Result } from '../../api/logs';
64
import { TypeAttributes } from 'rsuite/esm/@types/common';
7-
import DateCell from './date_cell/date_cell';
5+
import { renderColumn } from './render_columns';
6+
import {
7+
getLogs,
8+
getLogDescription,
9+
Result,
10+
FieldMetaData,
11+
} from '../../api/logs';
812
import 'rsuite/dist/rsuite.min.css';
913
import './log_table.scss';
1014

1115
export interface LogTableProps extends StandardProps {
12-
logLevels?: string;
13-
queryText?: string;
14-
jobID?: number;
15-
startDate?: Date;
16-
endDate?: Date;
16+
columns?: FieldMetaData[];
17+
filters?: any;
1718
}
1819

19-
export default function LogTable({
20-
logLevels,
21-
queryText,
22-
jobID,
23-
startDate,
24-
endDate,
25-
}: LogTableProps) {
20+
export default function LogTable({ columns, filters }: LogTableProps) {
2621
const [loading, setLoading] = useState<boolean>(false);
27-
const [logs, setLogs] = useState<Log[] | null>([]);
22+
const [logs, setLogs] = useState<any[] | null>([]);
2823
const [count, setCount] = useState<number>(0);
2924
const [page, setPage] = useState<number>(0);
3025
const [limit, setLimit] = useState<number>(20);
3126

27+
const renderedColumns = useMemo(
28+
() =>
29+
columns
30+
?.sort((a, b) => (a.type.length > b.type.length ? 1 : -1))
31+
.map((c, idx) => renderColumn(c, idx)),
32+
[columns]
33+
);
34+
3235
const toaster = useToaster();
3336
const pageSizes = [20, 50, 100];
3437

@@ -41,16 +44,14 @@ export default function LogTable({
4144
);
4245
};
4346
const updateLogsTable = async (page: number, limit: number) => {
47+
getLogDescription();
48+
4449
setLoading(true);
4550
try {
4651
let result: Result = await getLogs({
47-
job_id: jobID ?? undefined,
48-
text: queryText,
4952
page: page,
5053
page_size: limit,
51-
log_level: logLevels,
52-
start_date: startDate?.toJSON(),
53-
end_date: endDate?.toJSON(),
54+
...filters,
5455
});
5556

5657
setLogs(result.logs);
@@ -83,22 +84,7 @@ export default function LogTable({
8384
wordWrap="break-word"
8485
rowHeight={30}
8586
>
86-
<Column width={80} align="center" fixed>
87-
<HeaderCell>JobID</HeaderCell>
88-
<Cell className="log-table__cell" dataKey="job_id" />
89-
</Column>
90-
<Column width={250} align="center" fixed>
91-
<HeaderCell>Date</HeaderCell>
92-
<DateCell className="log-table__cell" dataKey="date" />
93-
</Column>
94-
<Column width={80} align="center" fixed>
95-
<HeaderCell>Level</HeaderCell>
96-
<Cell className="log-table__cell" dataKey="log_level" />
97-
</Column>
98-
<Column width={600} align="left" flexGrow={1}>
99-
<HeaderCell>Data</HeaderCell>
100-
<Cell className="log-table__cell" dataKey="log_data" />
101-
</Column>
87+
{renderedColumns}
10288
</Table>
10389
<div>
10490
<Pagination
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
import { FieldMetaData, FieldType } from '../../api/logs';
3+
import { Column, Cell, HeaderCell } from 'rsuite-table';
4+
import DateCell from './date_cell/date_cell';
5+
6+
export function renderColumn(field: FieldMetaData, key: any) {
7+
switch (field.type) {
8+
case FieldType.INT:
9+
case FieldType.UINT:
10+
case FieldType.ENUM:
11+
return (
12+
<Column width={80} align="center" fixed key={key}>
13+
<HeaderCell>{field.name}</HeaderCell>
14+
<Cell className="log-table__cell" dataKey={field.name} />
15+
</Column>
16+
);
17+
case FieldType.STRING:
18+
return (
19+
<Column width={600} align="left" flexGrow={1} key={key}>
20+
<HeaderCell>{field.name}</HeaderCell>
21+
<Cell className="log-table__cell" dataKey={field.name} />
22+
</Column>
23+
);
24+
case FieldType.TIME:
25+
return (
26+
<Column width={250} align="center" fixed key={key}>
27+
<HeaderCell>{field.name}</HeaderCell>
28+
<DateCell
29+
className="log-table__cell"
30+
dataKey={field.name}
31+
/>
32+
</Column>
33+
);
34+
}
35+
}

0 commit comments

Comments
 (0)