mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 10:49:03 +08:00
Merge branch 'develop' into feat/opamp-logparing
This commit is contained in:
commit
0fa20445d8
@ -1,6 +1,6 @@
|
|||||||
import MEditor, { EditorProps } from '@monaco-editor/react';
|
import MEditor, { EditorProps } from '@monaco-editor/react';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
function Editor({
|
function Editor({
|
||||||
value,
|
value,
|
||||||
@ -11,16 +11,24 @@ function Editor({
|
|||||||
options,
|
options,
|
||||||
}: MEditorProps): JSX.Element {
|
}: MEditorProps): JSX.Element {
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
|
const onChangeHandler = (newValue?: string): void => {
|
||||||
|
if (typeof newValue === 'string' && onChange) onChange(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const editorOptions = useMemo(
|
||||||
|
() => ({ fontSize: 16, automaticLayout: true, readOnly, ...options }),
|
||||||
|
[options, readOnly],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MEditor
|
<MEditor
|
||||||
theme={isDarkMode ? 'vs-dark' : 'vs-light'}
|
theme={isDarkMode ? 'vs-dark' : 'vs-light'}
|
||||||
language={language}
|
language={language}
|
||||||
value={value}
|
value={value}
|
||||||
options={{ fontSize: 16, automaticLayout: true, readOnly, ...options }}
|
options={editorOptions}
|
||||||
height={height}
|
height={height}
|
||||||
onChange={(newValue): void => {
|
onChange={onChangeHandler}
|
||||||
if (typeof newValue === 'string') onChange(newValue);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -28,7 +36,7 @@ function Editor({
|
|||||||
interface MEditorProps {
|
interface MEditorProps {
|
||||||
value: string;
|
value: string;
|
||||||
language?: string;
|
language?: string;
|
||||||
onChange: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
height?: string;
|
height?: string;
|
||||||
options?: EditorProps['options'];
|
options?: EditorProps['options'];
|
||||||
@ -39,6 +47,7 @@ Editor.defaultProps = {
|
|||||||
readOnly: false,
|
readOnly: false,
|
||||||
height: '40vh',
|
height: '40vh',
|
||||||
options: {},
|
options: {},
|
||||||
|
onChange: (): void => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Editor;
|
export default Editor;
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
import { blue, orange } from '@ant-design/colors';
|
import { blue, orange } from '@ant-design/colors';
|
||||||
import { Input } from 'antd';
|
import { Input } from 'antd';
|
||||||
|
import { ColumnsType } from 'antd/es/table';
|
||||||
|
import Editor from 'components/Editor';
|
||||||
import AddToQueryHOC from 'components/Logs/AddToQueryHOC';
|
import AddToQueryHOC from 'components/Logs/AddToQueryHOC';
|
||||||
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
|
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import flatten from 'flat';
|
import flatten from 'flat';
|
||||||
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
||||||
|
import { isEmpty } from 'lodash-es';
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
import ActionItem from './ActionItem';
|
import ActionItem from './ActionItem';
|
||||||
|
import { recursiveParseJSON } from './utils';
|
||||||
|
|
||||||
// Fields which should be restricted from adding it to query
|
// Fields which should be restricted from adding it to query
|
||||||
const RESTRICTED_FIELDS = ['timestamp'];
|
const RESTRICTED_FIELDS = ['timestamp'];
|
||||||
@ -41,10 +45,10 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = [
|
const columns: ColumnsType<DataType> = [
|
||||||
{
|
{
|
||||||
title: 'Action',
|
title: 'Action',
|
||||||
width: 100,
|
width: 15,
|
||||||
render: (fieldData: Record<string, string>): JSX.Element | null => {
|
render: (fieldData: Record<string, string>): JSX.Element | null => {
|
||||||
const fieldKey = fieldData.field.split('.').slice(-1);
|
const fieldKey = fieldData.field.split('.').slice(-1);
|
||||||
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
|
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
|
||||||
@ -57,7 +61,8 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
|
|||||||
title: 'Field',
|
title: 'Field',
|
||||||
dataIndex: 'field',
|
dataIndex: 'field',
|
||||||
key: 'field',
|
key: 'field',
|
||||||
width: 100,
|
width: 30,
|
||||||
|
ellipsis: true,
|
||||||
render: (field: string): JSX.Element => {
|
render: (field: string): JSX.Element => {
|
||||||
const fieldKey = field.split('.').slice(-1);
|
const fieldKey = field.split('.').slice(-1);
|
||||||
const renderedField = <span style={{ color: blue[4] }}>{field}</span>;
|
const renderedField = <span style={{ color: blue[4] }}>{field}</span>;
|
||||||
@ -78,16 +83,36 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
|
|||||||
key: 'value',
|
key: 'value',
|
||||||
width: 80,
|
width: 80,
|
||||||
ellipsis: false,
|
ellipsis: false,
|
||||||
render: (field: never): JSX.Element => (
|
render: (field, record): JSX.Element => {
|
||||||
|
if (record.field === 'body') {
|
||||||
|
const parsedBody = recursiveParseJSON(field);
|
||||||
|
if (!isEmpty(parsedBody)) {
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
value={JSON.stringify(parsedBody, null, 2)}
|
||||||
|
readOnly
|
||||||
|
height="70vh"
|
||||||
|
options={{
|
||||||
|
minimap: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<CopyClipboardHOC textToCopy={field}>
|
<CopyClipboardHOC textToCopy={field}>
|
||||||
<span style={{ color: orange[6] }}>{field}</span>
|
<span style={{ color: orange[6] }}>{field}</span>
|
||||||
</CopyClipboardHOC>
|
</CopyClipboardHOC>
|
||||||
),
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: 'relative' }}>
|
<>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search field names"
|
placeholder="Search field names"
|
||||||
size="large"
|
size="large"
|
||||||
@ -95,13 +120,19 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
|
|||||||
onChange={(e): void => setFieldSearchInput(e.target.value)}
|
onChange={(e): void => setFieldSearchInput(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<ResizeTable
|
<ResizeTable
|
||||||
columns={columns as never}
|
columns={columns}
|
||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DataType {
|
||||||
|
key: string;
|
||||||
|
field: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default TableView;
|
export default TableView;
|
||||||
|
47
frontend/src/container/LogDetailedView/util.test.ts
Normal file
47
frontend/src/container/LogDetailedView/util.test.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { recursiveParseJSON } from './utils';
|
||||||
|
|
||||||
|
describe('recursiveParseJSON', () => {
|
||||||
|
it('should return an empty object if the input is not valid JSON', () => {
|
||||||
|
const result = recursiveParseJSON('not valid JSON');
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the parsed JSON object for valid JSON input', () => {
|
||||||
|
const jsonString = '{"name": "John", "age": 30}';
|
||||||
|
const result = recursiveParseJSON(jsonString);
|
||||||
|
expect(result).toEqual({ name: 'John', age: 30 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should recursively parse nested JSON objects', () => {
|
||||||
|
const jsonString =
|
||||||
|
'{"name": "John", "age": 30, "address": {"street": "123 Main St", "city": "Anytown", "state": "CA"}}';
|
||||||
|
const result = recursiveParseJSON(jsonString);
|
||||||
|
expect(result).toEqual({
|
||||||
|
name: 'John',
|
||||||
|
age: 30,
|
||||||
|
address: {
|
||||||
|
street: '123 Main St',
|
||||||
|
city: 'Anytown',
|
||||||
|
state: 'CA',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should recursively parse nested JSON arrays', () => {
|
||||||
|
const jsonString = '[1, 2, [3, 4], {"foo": "bar"}]';
|
||||||
|
const result = recursiveParseJSON(jsonString);
|
||||||
|
expect(result).toEqual([1, 2, [3, 4], { foo: 'bar' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should recursively parse deeply nested JSON objects', () => {
|
||||||
|
const jsonString = '{"foo": {"bar": {"baz": {"qux": {"value": 42}}}}}';
|
||||||
|
const result = recursiveParseJSON(jsonString);
|
||||||
|
expect(result).toEqual({ foo: { bar: { baz: { qux: { value: 42 } } } } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle JSON input that contains escaped characters', () => {
|
||||||
|
const jsonString = '{"name": "John\\", \\"Doe", "age": 30}';
|
||||||
|
const result = recursiveParseJSON(jsonString);
|
||||||
|
expect(result).toEqual({ name: 'John", "Doe', age: 30 });
|
||||||
|
});
|
||||||
|
});
|
11
frontend/src/container/LogDetailedView/utils.ts
Normal file
11
frontend/src/container/LogDetailedView/utils.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export const recursiveParseJSON = (obj: string): Record<string, unknown> => {
|
||||||
|
try {
|
||||||
|
const value = JSON.parse(obj);
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return recursiveParseJSON(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user