mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-06-21 00:37:58 +08:00
Feat/list infinity scroll (#2992)
* feat: add custom orderBy * feat: infinity scroll list logs list * feat: add infinity table view * Fix/double query logs request (#3006) * feat: add control panel * fix: repeating query api request * fix: scroll, remove id, page size * fix: reset offset to 0 * feat: add log explorer detail (#3007) * feat: add control panel * fix: repeating query api request * feat: add log explorer detail --------- Co-authored-by: Vishal Sharma <makeavish786@gmail.com> * feat: add group by in the logs chart (#3009) * feat: add control panel * fix: repeating query api request * feat: add log explorer detail * feat: add group by in the logs chart * fix: list timestamp, limit, filter order * feat: add list chart (#3037) * feat: add list chart * refactor: remove console log * feat: hide aggregate every for table view (#3046) * feat: hide aggregate every for table view * fix: text filter for inactive filters * refactor: remove log * fix: table columns * fix: timestamp type --------- Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
This commit is contained in:
parent
8363dadd8d
commit
76ba364317
@ -0,0 +1,3 @@
|
|||||||
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
|
export type LogDetailProps = { log: ILog | null; onClose: () => void };
|
41
frontend/src/components/LogDetail/index.tsx
Normal file
41
frontend/src/components/LogDetail/index.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { Drawer, Tabs } from 'antd';
|
||||||
|
import JSONView from 'container/LogDetailedView/JsonView';
|
||||||
|
import TableView from 'container/LogDetailedView/TableView';
|
||||||
|
|
||||||
|
import { LogDetailProps } from './LogDetail.interfaces';
|
||||||
|
|
||||||
|
function LogDetail({ log, onClose }: LogDetailProps): JSX.Element {
|
||||||
|
const onDrawerClose = (): void => {
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
label: 'Table',
|
||||||
|
key: '1',
|
||||||
|
children: log && <TableView logData={log} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'JSON',
|
||||||
|
key: '2',
|
||||||
|
children: log && <JSONView logData={log} />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
width="60%"
|
||||||
|
title="Log Details"
|
||||||
|
placement="right"
|
||||||
|
closable
|
||||||
|
onClose={onDrawerClose}
|
||||||
|
open={log !== null}
|
||||||
|
style={{ overscrollBehavior: 'contain' }}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<Tabs defaultActiveKey="1" items={items} />
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LogDetail;
|
@ -8,13 +8,10 @@ import { useNotifications } from 'hooks/useNotifications';
|
|||||||
// utils
|
// utils
|
||||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
|
||||||
import { useCopyToClipboard } from 'react-use';
|
import { useCopyToClipboard } from 'react-use';
|
||||||
// interfaces
|
// interfaces
|
||||||
import { AppState } from 'store/reducers';
|
import { IField } from 'types/api/logs/fields';
|
||||||
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
|
||||||
|
|
||||||
// components
|
// components
|
||||||
import AddToQueryHOC from '../AddToQueryHOC';
|
import AddToQueryHOC from '../AddToQueryHOC';
|
||||||
@ -79,24 +76,22 @@ function LogSelectedField({
|
|||||||
|
|
||||||
interface ListLogViewProps {
|
interface ListLogViewProps {
|
||||||
logData: ILog;
|
logData: ILog;
|
||||||
|
onOpenDetailedView: (log: ILog) => void;
|
||||||
|
selectedFields: IField[];
|
||||||
}
|
}
|
||||||
function ListLogView({ logData }: ListLogViewProps): JSX.Element {
|
function ListLogView({
|
||||||
const {
|
logData,
|
||||||
fields: { selected },
|
selectedFields,
|
||||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
onOpenDetailedView,
|
||||||
|
}: ListLogViewProps): JSX.Element {
|
||||||
const dispatch = useDispatch();
|
|
||||||
const flattenLogData = useMemo(() => FlatLogData(logData), [logData]);
|
const flattenLogData = useMemo(() => FlatLogData(logData), [logData]);
|
||||||
|
|
||||||
const [, setCopy] = useCopyToClipboard();
|
const [, setCopy] = useCopyToClipboard();
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
const handleDetailedView = useCallback(() => {
|
const handleDetailedView = useCallback(() => {
|
||||||
dispatch({
|
onOpenDetailedView(logData);
|
||||||
type: SET_DETAILED_LOG_DATA,
|
}, [logData, onOpenDetailedView]);
|
||||||
payload: logData,
|
|
||||||
});
|
|
||||||
}, [dispatch, logData]);
|
|
||||||
|
|
||||||
const handleCopyJSON = (): void => {
|
const handleCopyJSON = (): void => {
|
||||||
setCopy(JSON.stringify(logData, null, 2));
|
setCopy(JSON.stringify(logData, null, 2));
|
||||||
@ -106,8 +101,16 @@ function ListLogView({ logData }: ListLogViewProps): JSX.Element {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updatedSelecedFields = useMemo(
|
const updatedSelecedFields = useMemo(
|
||||||
() => selected.filter((e) => e.name !== 'id'),
|
() => selectedFields.filter((e) => e.name !== 'id'),
|
||||||
[selected],
|
[selectedFields],
|
||||||
|
);
|
||||||
|
|
||||||
|
const timestampValue = useMemo(
|
||||||
|
() =>
|
||||||
|
typeof flattenLogData.timestamp === 'string'
|
||||||
|
? dayjs(flattenLogData.timestamp).format()
|
||||||
|
: dayjs(flattenLogData.timestamp / 1e6).format(),
|
||||||
|
[flattenLogData.timestamp],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -119,10 +122,7 @@ function ListLogView({ logData }: ListLogViewProps): JSX.Element {
|
|||||||
{flattenLogData.stream && (
|
{flattenLogData.stream && (
|
||||||
<LogGeneralField fieldKey="stream" fieldValue={flattenLogData.stream} />
|
<LogGeneralField fieldKey="stream" fieldValue={flattenLogData.stream} />
|
||||||
)}
|
)}
|
||||||
<LogGeneralField
|
<LogGeneralField fieldKey="timestamp" fieldValue={timestampValue} />
|
||||||
fieldKey="timestamp"
|
|
||||||
fieldValue={dayjs((flattenLogData.timestamp as never) / 1e6).format()}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
</LogContainer>
|
</LogContainer>
|
||||||
<div>
|
<div>
|
||||||
|
@ -30,7 +30,10 @@ function RawLogView(props: RawLogViewProps): JSX.Element {
|
|||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
const text = useMemo(
|
const text = useMemo(
|
||||||
() => `${dayjs(data.timestamp / 1e6).format()} | ${data.body}`,
|
() =>
|
||||||
|
typeof data.timestamp === 'string'
|
||||||
|
? `${dayjs(data.timestamp).format()} | ${data.body}`
|
||||||
|
: `${dayjs(data.timestamp / 1e6).format()} | ${data.body}`,
|
||||||
[data.timestamp, data.body],
|
[data.timestamp, data.body],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,121 +1,18 @@
|
|||||||
import { ExpandAltOutlined } from '@ant-design/icons';
|
import { Table } from 'antd';
|
||||||
import Convert from 'ansi-to-html';
|
|
||||||
import { Table, Typography } from 'antd';
|
|
||||||
import { ColumnsType, ColumnType } from 'antd/es/table';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import dompurify from 'dompurify';
|
|
||||||
// utils
|
|
||||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { IField } from 'types/api/logs/fields';
|
|
||||||
// interfaces
|
|
||||||
import { ILog } from 'types/api/logs/log';
|
|
||||||
|
|
||||||
// styles
|
|
||||||
import { ExpandIconWrapper } from '../RawLogView/styles';
|
|
||||||
// config
|
// config
|
||||||
import { defaultCellStyle, defaultTableStyle, tableScroll } from './config';
|
import { tableScroll } from './config';
|
||||||
import { TableBodyContent } from './styles';
|
import { LogsTableViewProps } from './types';
|
||||||
|
import { useTableView } from './useTableView';
|
||||||
type ColumnTypeRender<T = unknown> = ReturnType<
|
|
||||||
NonNullable<ColumnType<T>['render']>
|
|
||||||
>;
|
|
||||||
|
|
||||||
type LogsTableViewProps = {
|
|
||||||
logs: ILog[];
|
|
||||||
fields: IField[];
|
|
||||||
linesPerRow: number;
|
|
||||||
onClickExpand: (log: ILog) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const convert = new Convert();
|
|
||||||
|
|
||||||
function LogsTableView(props: LogsTableViewProps): JSX.Element {
|
function LogsTableView(props: LogsTableViewProps): JSX.Element {
|
||||||
const { logs, fields, linesPerRow, onClickExpand } = props;
|
const { dataSource, columns } = useTableView(props);
|
||||||
|
|
||||||
const flattenLogData = useMemo(() => logs.map((log) => FlatLogData(log)), [
|
|
||||||
logs,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const columns: ColumnsType<Record<string, unknown>> = useMemo(() => {
|
|
||||||
const fieldColumns: ColumnsType<Record<string, unknown>> = fields
|
|
||||||
.filter((e) => e.name !== 'id')
|
|
||||||
.map(({ name }) => ({
|
|
||||||
title: name,
|
|
||||||
dataIndex: name,
|
|
||||||
key: name,
|
|
||||||
render: (field): ColumnTypeRender<Record<string, unknown>> => ({
|
|
||||||
props: {
|
|
||||||
style: defaultCellStyle,
|
|
||||||
},
|
|
||||||
children: (
|
|
||||||
<Typography.Paragraph ellipsis={{ rows: linesPerRow }}>
|
|
||||||
{field}
|
|
||||||
</Typography.Paragraph>
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
title: '',
|
|
||||||
dataIndex: 'id',
|
|
||||||
key: 'expand',
|
|
||||||
// https://github.com/ant-design/ant-design/discussions/36886
|
|
||||||
render: (_, item): ColumnTypeRender<Record<string, unknown>> => ({
|
|
||||||
props: {
|
|
||||||
style: defaultCellStyle,
|
|
||||||
},
|
|
||||||
children: (
|
|
||||||
<ExpandIconWrapper
|
|
||||||
onClick={(): void => {
|
|
||||||
onClickExpand((item as unknown) as ILog);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ExpandAltOutlined />
|
|
||||||
</ExpandIconWrapper>
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'timestamp',
|
|
||||||
dataIndex: 'timestamp',
|
|
||||||
key: 'timestamp',
|
|
||||||
// https://github.com/ant-design/ant-design/discussions/36886
|
|
||||||
render: (field): ColumnTypeRender<Record<string, unknown>> => {
|
|
||||||
const date = dayjs(field / 1e6).format();
|
|
||||||
return {
|
|
||||||
children: <Typography.Paragraph ellipsis>{date}</Typography.Paragraph>,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
...fieldColumns,
|
|
||||||
{
|
|
||||||
title: 'body',
|
|
||||||
dataIndex: 'body',
|
|
||||||
key: 'body',
|
|
||||||
render: (field): ColumnTypeRender<Record<string, unknown>> => ({
|
|
||||||
props: {
|
|
||||||
style: defaultTableStyle,
|
|
||||||
},
|
|
||||||
children: (
|
|
||||||
<TableBodyContent
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: convert.toHtml(dompurify.sanitize(field)),
|
|
||||||
}}
|
|
||||||
linesPerRow={linesPerRow}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [fields, linesPerRow, onClickExpand]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
size="small"
|
size="small"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={flattenLogData}
|
dataSource={dataSource}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
bordered
|
bordered
|
||||||
|
14
frontend/src/components/Logs/TableView/types.ts
Normal file
14
frontend/src/components/Logs/TableView/types.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { ColumnType } from 'antd/es/table';
|
||||||
|
import { IField } from 'types/api/logs/fields';
|
||||||
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
|
export type ColumnTypeRender<T = unknown> = ReturnType<
|
||||||
|
NonNullable<ColumnType<T>['render']>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type LogsTableViewProps = {
|
||||||
|
logs: ILog[];
|
||||||
|
fields: IField[];
|
||||||
|
linesPerRow: number;
|
||||||
|
onClickExpand: (log: ILog) => void;
|
||||||
|
};
|
108
frontend/src/components/Logs/TableView/useTableView.tsx
Normal file
108
frontend/src/components/Logs/TableView/useTableView.tsx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { ExpandAltOutlined } from '@ant-design/icons';
|
||||||
|
import Convert from 'ansi-to-html';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
import { ColumnsType } from 'antd/es/table';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import dompurify from 'dompurify';
|
||||||
|
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
|
import { ExpandIconWrapper } from '../RawLogView/styles';
|
||||||
|
import { defaultCellStyle, defaultTableStyle } from './config';
|
||||||
|
import { TableBodyContent } from './styles';
|
||||||
|
import { ColumnTypeRender, LogsTableViewProps } from './types';
|
||||||
|
|
||||||
|
export type UseTableViewResult = {
|
||||||
|
columns: ColumnsType<Record<string, unknown>>;
|
||||||
|
dataSource: Record<string, string>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const convert = new Convert();
|
||||||
|
|
||||||
|
export const useTableView = (props: LogsTableViewProps): UseTableViewResult => {
|
||||||
|
const { logs, fields, linesPerRow, onClickExpand } = props;
|
||||||
|
|
||||||
|
const flattenLogData = useMemo(() => logs.map((log) => FlatLogData(log)), [
|
||||||
|
logs,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const columns: ColumnsType<Record<string, unknown>> = useMemo(() => {
|
||||||
|
const fieldColumns: ColumnsType<Record<string, unknown>> = fields
|
||||||
|
.filter((e) => e.name !== 'id')
|
||||||
|
.map(({ name }) => ({
|
||||||
|
title: name,
|
||||||
|
dataIndex: name,
|
||||||
|
key: name,
|
||||||
|
render: (field): ColumnTypeRender<Record<string, unknown>> => ({
|
||||||
|
props: {
|
||||||
|
style: defaultCellStyle,
|
||||||
|
},
|
||||||
|
children: (
|
||||||
|
<Typography.Paragraph ellipsis={{ rows: linesPerRow }}>
|
||||||
|
{field}
|
||||||
|
</Typography.Paragraph>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
dataIndex: 'id',
|
||||||
|
key: 'expand',
|
||||||
|
// https://github.com/ant-design/ant-design/discussions/36886
|
||||||
|
render: (_, item): ColumnTypeRender<Record<string, unknown>> => ({
|
||||||
|
props: {
|
||||||
|
style: defaultCellStyle,
|
||||||
|
},
|
||||||
|
children: (
|
||||||
|
<ExpandIconWrapper
|
||||||
|
onClick={(): void => {
|
||||||
|
onClickExpand((item as unknown) as ILog);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ExpandAltOutlined />
|
||||||
|
</ExpandIconWrapper>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'timestamp',
|
||||||
|
dataIndex: 'timestamp',
|
||||||
|
key: 'timestamp',
|
||||||
|
// https://github.com/ant-design/ant-design/discussions/36886
|
||||||
|
render: (field): ColumnTypeRender<Record<string, unknown>> => {
|
||||||
|
const date =
|
||||||
|
typeof field === 'string'
|
||||||
|
? dayjs(field).format()
|
||||||
|
: dayjs(field / 1e6).format();
|
||||||
|
return {
|
||||||
|
children: <Typography.Paragraph ellipsis>{date}</Typography.Paragraph>,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...fieldColumns,
|
||||||
|
{
|
||||||
|
title: 'body',
|
||||||
|
dataIndex: 'body',
|
||||||
|
key: 'body',
|
||||||
|
render: (field): ColumnTypeRender<Record<string, unknown>> => ({
|
||||||
|
props: {
|
||||||
|
style: defaultTableStyle,
|
||||||
|
},
|
||||||
|
children: (
|
||||||
|
<TableBodyContent
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: convert.toHtml(dompurify.sanitize(field)),
|
||||||
|
}}
|
||||||
|
linesPerRow={linesPerRow}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [fields, linesPerRow, onClickExpand]);
|
||||||
|
|
||||||
|
return { columns, dataSource: flattenLogData };
|
||||||
|
};
|
@ -14,6 +14,7 @@ import {
|
|||||||
IPromQLQuery,
|
IPromQLQuery,
|
||||||
Query,
|
Query,
|
||||||
QueryState,
|
QueryState,
|
||||||
|
TagFilter,
|
||||||
} from 'types/api/queryBuilder/queryBuilderData';
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
import {
|
import {
|
||||||
@ -113,6 +114,11 @@ export const initialAutocompleteData: BaseAutocompleteData = {
|
|||||||
type: null,
|
type: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const initialFilters: TagFilter = {
|
||||||
|
items: [],
|
||||||
|
op: 'AND',
|
||||||
|
};
|
||||||
|
|
||||||
const initialQueryBuilderFormValues: IBuilderQuery = {
|
const initialQueryBuilderFormValues: IBuilderQuery = {
|
||||||
dataSource: DataSource.METRICS,
|
dataSource: DataSource.METRICS,
|
||||||
queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
|
queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
|
||||||
|
@ -1,2 +1,16 @@
|
|||||||
export const COMPOSITE_QUERY = 'compositeQuery';
|
type QueryParamNames =
|
||||||
export const PANEL_TYPES_QUERY = 'panelTypes';
|
| 'compositeQuery'
|
||||||
|
| 'panelTypes'
|
||||||
|
| 'pageSize'
|
||||||
|
| 'viewMode'
|
||||||
|
| 'selectedFields'
|
||||||
|
| 'linesPerRow';
|
||||||
|
|
||||||
|
export const queryParamNamesMap: Record<QueryParamNames, QueryParamNames> = {
|
||||||
|
compositeQuery: 'compositeQuery',
|
||||||
|
panelTypes: 'panelTypes',
|
||||||
|
pageSize: 'pageSize',
|
||||||
|
viewMode: 'viewMode',
|
||||||
|
selectedFields: 'selectedFields',
|
||||||
|
linesPerRow: 'linesPerRow',
|
||||||
|
};
|
||||||
|
@ -2,6 +2,8 @@ import { CSSProperties } from 'react';
|
|||||||
|
|
||||||
export const ITEMS_PER_PAGE_OPTIONS = [25, 50, 100, 200];
|
export const ITEMS_PER_PAGE_OPTIONS = [25, 50, 100, 200];
|
||||||
|
|
||||||
|
export const DEFAULT_PER_PAGE_VALUE = 100;
|
||||||
|
|
||||||
export const defaultSelectStyle: CSSProperties = {
|
export const defaultSelectStyle: CSSProperties = {
|
||||||
minWidth: '6rem',
|
minWidth: '6rem',
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
import { OptionsMenuConfig } from 'container/OptionsMenu/types';
|
||||||
|
|
||||||
|
export type ExplorerControlPanelProps = {
|
||||||
|
isShowPageSize: boolean;
|
||||||
|
isLoading: boolean;
|
||||||
|
optionsMenuConfig?: OptionsMenuConfig;
|
||||||
|
};
|
29
frontend/src/container/ExplorerControlPanel/index.tsx
Normal file
29
frontend/src/container/ExplorerControlPanel/index.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Col, Row } from 'antd';
|
||||||
|
import OptionsMenu from 'container/OptionsMenu';
|
||||||
|
import PageSizeSelect from 'container/PageSizeSelect';
|
||||||
|
|
||||||
|
import { ExplorerControlPanelProps } from './ExplorerControlPanel.interfaces';
|
||||||
|
import { ContainerStyled } from './styles';
|
||||||
|
|
||||||
|
function ExplorerControlPanel({
|
||||||
|
isLoading,
|
||||||
|
isShowPageSize,
|
||||||
|
optionsMenuConfig,
|
||||||
|
}: ExplorerControlPanelProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<ContainerStyled>
|
||||||
|
<Row justify="end" gutter={30}>
|
||||||
|
{optionsMenuConfig && (
|
||||||
|
<Col>
|
||||||
|
<OptionsMenu config={optionsMenuConfig} />
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
<Col>
|
||||||
|
<PageSizeSelect isLoading={isLoading} isShow={isShowPageSize} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</ContainerStyled>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExplorerControlPanel;
|
5
frontend/src/container/ExplorerControlPanel/styles.ts
Normal file
5
frontend/src/container/ExplorerControlPanel/styles.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const ContainerStyled = styled.div`
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
`;
|
@ -1,5 +1,5 @@
|
|||||||
import { Button, Dropdown, MenuProps, Modal } from 'antd';
|
import { Button, Dropdown, MenuProps, Modal } from 'antd';
|
||||||
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
|
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
@ -22,9 +22,9 @@ function ExportPanel({
|
|||||||
|
|
||||||
const onCreateAlertsHandler = useCallback(() => {
|
const onCreateAlertsHandler = useCallback(() => {
|
||||||
history.push(
|
history.push(
|
||||||
`${ROUTES.ALERTS_NEW}?${COMPOSITE_QUERY}=${encodeURIComponent(
|
`${ROUTES.ALERTS_NEW}?${
|
||||||
JSON.stringify(query),
|
queryParamNamesMap.compositeQuery
|
||||||
)}`,
|
}=${encodeURIComponent(JSON.stringify(query))}`,
|
||||||
);
|
);
|
||||||
}, [query]);
|
}, [query]);
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
import { Dropdown, MenuProps, Tooltip, Typography } from 'antd';
|
import { Dropdown, MenuProps, Tooltip, Typography } from 'antd';
|
||||||
import { MenuItemType } from 'antd/es/menu/hooks/useItems';
|
import { MenuItemType } from 'antd/es/menu/hooks/useItems';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
|
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
@ -64,7 +64,9 @@ function WidgetHeader({
|
|||||||
history.push(
|
history.push(
|
||||||
`${window.location.pathname}/new?widgetId=${widgetId}&graphType=${
|
`${window.location.pathname}/new?widgetId=${widgetId}&graphType=${
|
||||||
widget.panelTypes
|
widget.panelTypes
|
||||||
}&${COMPOSITE_QUERY}=${encodeURIComponent(JSON.stringify(widget.query))}`,
|
}&${queryParamNamesMap.compositeQuery}=${encodeURIComponent(
|
||||||
|
JSON.stringify(widget.query),
|
||||||
|
)}`,
|
||||||
);
|
);
|
||||||
}, [widget.id, widget.panelTypes, widget.query]);
|
}, [widget.id, widget.panelTypes, widget.query]);
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { ColumnsType } from 'antd/lib/table';
|
|||||||
import saveAlertApi from 'api/alerts/save';
|
import saveAlertApi from 'api/alerts/save';
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import TextToolTip from 'components/TextToolTip';
|
import TextToolTip from 'components/TextToolTip';
|
||||||
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
|
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
import useInterval from 'hooks/useInterval';
|
import useInterval from 'hooks/useInterval';
|
||||||
@ -75,11 +75,9 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
const compositeQuery = mapQueryDataFromApi(record.condition.compositeQuery);
|
const compositeQuery = mapQueryDataFromApi(record.condition.compositeQuery);
|
||||||
|
|
||||||
history.push(
|
history.push(
|
||||||
`${
|
`${ROUTES.EDIT_ALERTS}?ruleId=${record.id.toString()}&${
|
||||||
ROUTES.EDIT_ALERTS
|
queryParamNamesMap.compositeQuery
|
||||||
}?ruleId=${record.id.toString()}&${COMPOSITE_QUERY}=${encodeURIComponent(
|
}=${encodeURIComponent(JSON.stringify(compositeQuery))}`,
|
||||||
JSON.stringify(compositeQuery),
|
|
||||||
)}`,
|
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch(handleError);
|
.catch(handleError);
|
||||||
|
@ -83,12 +83,17 @@ function LogControls(): JSX.Element | null {
|
|||||||
|
|
||||||
const flattenLogData = useMemo(
|
const flattenLogData = useMemo(
|
||||||
() =>
|
() =>
|
||||||
logs.map((log) =>
|
logs.map((log) => {
|
||||||
FlatLogData({
|
const timestamp =
|
||||||
|
typeof log.timestamp === 'string'
|
||||||
|
? dayjs(log.timestamp).format()
|
||||||
|
: dayjs(log.timestamp / 1e6).format();
|
||||||
|
|
||||||
|
return FlatLogData({
|
||||||
...log,
|
...log,
|
||||||
timestamp: (dayjs(log.timestamp / 1e6).format() as unknown) as number,
|
timestamp,
|
||||||
}),
|
});
|
||||||
),
|
}),
|
||||||
[logs],
|
[logs],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Drawer, Tabs } from 'antd';
|
import LogDetail from 'components/LogDetail';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@ -6,9 +6,6 @@ import AppActions from 'types/actions';
|
|||||||
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
|
|
||||||
import JSONView from './JsonView';
|
|
||||||
import TableView from './TableView';
|
|
||||||
|
|
||||||
function LogDetailedView(): JSX.Element {
|
function LogDetailedView(): JSX.Element {
|
||||||
const { detailedLog } = useSelector<AppState, ILogsReducer>(
|
const { detailedLog } = useSelector<AppState, ILogsReducer>(
|
||||||
(state) => state.logs,
|
(state) => state.logs,
|
||||||
@ -23,33 +20,7 @@ function LogDetailedView(): JSX.Element {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const items = [
|
return <LogDetail log={detailedLog} onClose={onDrawerClose} />;
|
||||||
{
|
|
||||||
label: 'Table',
|
|
||||||
key: '1',
|
|
||||||
children: detailedLog && <TableView logData={detailedLog} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'JSON',
|
|
||||||
key: '2',
|
|
||||||
children: detailedLog && <JSONView logData={detailedLog} />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Drawer
|
|
||||||
width="60%"
|
|
||||||
title="Log Details"
|
|
||||||
placement="right"
|
|
||||||
closable
|
|
||||||
onClose={onDrawerClose}
|
|
||||||
open={detailedLog !== null}
|
|
||||||
style={{ overscrollBehavior: 'contain' }}
|
|
||||||
destroyOnClose
|
|
||||||
>
|
|
||||||
<Tabs defaultActiveKey="1" items={items} />
|
|
||||||
</Drawer>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LogDetailedView;
|
export default LogDetailedView;
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
|
export type LogExplorerDetailedViewProps = {
|
||||||
|
log: ILog | null;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
16
frontend/src/container/LogExplorerDetailedView/index.tsx
Normal file
16
frontend/src/container/LogExplorerDetailedView/index.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import LogDetail from 'components/LogDetail';
|
||||||
|
|
||||||
|
import { LogExplorerDetailedViewProps } from './LogExplorerDetailedView.interfaces';
|
||||||
|
|
||||||
|
function LogExplorerDetailedView({
|
||||||
|
log,
|
||||||
|
onClose,
|
||||||
|
}: LogExplorerDetailedViewProps): JSX.Element {
|
||||||
|
const onDrawerClose = (): void => {
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return <LogDetail log={log} onClose={onDrawerClose} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LogExplorerDetailedView;
|
@ -0,0 +1,6 @@
|
|||||||
|
import { QueryData } from 'types/api/widgets/getQuery';
|
||||||
|
|
||||||
|
export type LogsExplorerChartProps = {
|
||||||
|
data: QueryData[];
|
||||||
|
isLoading: boolean;
|
||||||
|
};
|
@ -1,48 +1,43 @@
|
|||||||
import Graph from 'components/Graph';
|
import Graph from 'components/Graph';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
import getChartData, { GetChartDataProps } from 'lib/getChartData';
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { colors } from 'lib/getRandomColor';
|
||||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
|
||||||
import { getExplorerChartData } from 'lib/explorer/getExplorerChartData';
|
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
|
||||||
|
|
||||||
|
import { LogsExplorerChartProps } from './LogsExplorerChart.interfaces';
|
||||||
import { CardStyled } from './LogsExplorerChart.styled';
|
import { CardStyled } from './LogsExplorerChart.styled';
|
||||||
|
|
||||||
function LogsExplorerChart(): JSX.Element {
|
function LogsExplorerChart({
|
||||||
const { stagedQuery, panelType, isEnabledQuery } = useQueryBuilder();
|
data,
|
||||||
|
isLoading,
|
||||||
|
}: LogsExplorerChartProps): JSX.Element {
|
||||||
|
const handleCreateDatasets: Required<GetChartDataProps>['createDataset'] = (
|
||||||
|
element,
|
||||||
|
index,
|
||||||
|
allLabels,
|
||||||
|
) => ({
|
||||||
|
label: allLabels[index],
|
||||||
|
data: element,
|
||||||
|
backgroundColor: colors[index % colors.length] || 'red',
|
||||||
|
borderColor: colors[index % colors.length] || 'red',
|
||||||
|
});
|
||||||
|
|
||||||
const { selectedTime } = useSelector<AppState, GlobalReducer>(
|
const graphData = useMemo(
|
||||||
(state) => state.globalTime,
|
() =>
|
||||||
|
getChartData({
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
queryData: data,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
createDataset: handleCreateDatasets,
|
||||||
|
}),
|
||||||
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, isFetching } = useGetQueryRange(
|
|
||||||
{
|
|
||||||
query: stagedQuery || initialQueriesMap.metrics,
|
|
||||||
graphType: panelType || PANEL_TYPES.LIST,
|
|
||||||
globalSelectedInterval: selectedTime,
|
|
||||||
selectedTime: 'GLOBAL_TIME',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
queryKey: [REACT_QUERY_KEY.GET_QUERY_RANGE, selectedTime, stagedQuery],
|
|
||||||
enabled: isEnabledQuery,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const graphData = useMemo(() => {
|
|
||||||
if (data?.payload.data && data.payload.data.result.length > 0) {
|
|
||||||
return getExplorerChartData([data.payload.data.result[0]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getExplorerChartData([]);
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardStyled>
|
<CardStyled>
|
||||||
{isFetching ? (
|
{isLoading ? (
|
||||||
<Spinner size="default" height="100%" />
|
<Spinner size="default" height="100%" />
|
||||||
) : (
|
) : (
|
||||||
<Graph
|
<Graph
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
import { CSSProperties } from 'react';
|
||||||
|
|
||||||
|
export const infinityDefaultStyles: CSSProperties = {
|
||||||
|
height: 'auto',
|
||||||
|
width: '100%',
|
||||||
|
};
|
@ -0,0 +1,98 @@
|
|||||||
|
import { ColumnTypeRender } from 'components/Logs/TableView/types';
|
||||||
|
import { useTableView } from 'components/Logs/TableView/useTableView';
|
||||||
|
import { cloneElement, ReactElement, ReactNode, useCallback } from 'react';
|
||||||
|
import { TableComponents, TableVirtuoso } from 'react-virtuoso';
|
||||||
|
|
||||||
|
import { infinityDefaultStyles } from './config';
|
||||||
|
import {
|
||||||
|
TableCellStyled,
|
||||||
|
TableHeaderCellStyled,
|
||||||
|
TableRowStyled,
|
||||||
|
TableStyled,
|
||||||
|
} from './styles';
|
||||||
|
import { InfinityTableProps } from './types';
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/function-component-definition
|
||||||
|
const CustomTable: TableComponents['Table'] = ({ style, children }) => (
|
||||||
|
<TableStyled style={style}>{children}</TableStyled>
|
||||||
|
);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/function-component-definition
|
||||||
|
const CustomTableRow: TableComponents['TableRow'] = ({
|
||||||
|
children,
|
||||||
|
context,
|
||||||
|
...props
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
}) => <TableRowStyled {...props}>{children}</TableRowStyled>;
|
||||||
|
|
||||||
|
function InfinityTable({
|
||||||
|
tableViewProps,
|
||||||
|
infitiyTableProps,
|
||||||
|
}: InfinityTableProps): JSX.Element | null {
|
||||||
|
const { onEndReached } = infitiyTableProps;
|
||||||
|
const { dataSource, columns } = useTableView(tableViewProps);
|
||||||
|
|
||||||
|
const itemContent = useCallback(
|
||||||
|
(index: number, log: Record<string, unknown>): JSX.Element => (
|
||||||
|
<>
|
||||||
|
{columns.map((column) => {
|
||||||
|
if (!column.render) return <td>Empty</td>;
|
||||||
|
|
||||||
|
const element: ColumnTypeRender<Record<string, unknown>> = column.render(
|
||||||
|
log[column.key as keyof Record<string, unknown>],
|
||||||
|
log,
|
||||||
|
index,
|
||||||
|
);
|
||||||
|
|
||||||
|
const elementWithChildren = element as Exclude<
|
||||||
|
ColumnTypeRender<Record<string, unknown>>,
|
||||||
|
ReactNode
|
||||||
|
>;
|
||||||
|
|
||||||
|
const children = elementWithChildren.children as ReactElement;
|
||||||
|
const props = elementWithChildren.props as Record<string, unknown>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableCellStyled key={column.key}>
|
||||||
|
{cloneElement(children, props)}
|
||||||
|
</TableCellStyled>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
[columns],
|
||||||
|
);
|
||||||
|
|
||||||
|
const tableHeader = useCallback(
|
||||||
|
() => (
|
||||||
|
<tr>
|
||||||
|
{columns.map((column) => (
|
||||||
|
<TableHeaderCellStyled key={column.key}>
|
||||||
|
{column.title as string}
|
||||||
|
</TableHeaderCellStyled>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
),
|
||||||
|
[columns],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableVirtuoso
|
||||||
|
style={infinityDefaultStyles}
|
||||||
|
data={dataSource}
|
||||||
|
components={{
|
||||||
|
Table: CustomTable,
|
||||||
|
// TODO: fix it in the future
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
TableRow: CustomTableRow,
|
||||||
|
}}
|
||||||
|
itemContent={itemContent}
|
||||||
|
fixedHeaderContent={tableHeader}
|
||||||
|
endReached={onEndReached}
|
||||||
|
totalCount={dataSource.length}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InfinityTable;
|
@ -0,0 +1,40 @@
|
|||||||
|
import { themeColors } from 'constants/theme';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const TableStyled = styled.table`
|
||||||
|
width: 100%;
|
||||||
|
border-top: 1px solid rgba(253, 253, 253, 0.12);
|
||||||
|
border-radius: 2px 2px 0 0;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
border-inline-start: 1px solid rgba(253, 253, 253, 0.12);
|
||||||
|
border-inline-end: 1px solid rgba(253, 253, 253, 0.12);
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TableCellStyled = styled.td`
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-inline-end: 1px solid rgba(253, 253, 253, 0.12);
|
||||||
|
border-top: 1px solid rgba(253, 253, 253, 0.12);
|
||||||
|
background-color: ${themeColors.lightBlack};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TableRowStyled = styled.tr`
|
||||||
|
&:hover {
|
||||||
|
${TableCellStyled} {
|
||||||
|
background-color: #1d1d1d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TableHeaderCellStyled = styled.th`
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-inline-end: 1px solid rgba(253, 253, 253, 0.12);
|
||||||
|
background-color: #1d1d1d;
|
||||||
|
&:first-child {
|
||||||
|
border-start-start-radius: 2px;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
border-start-end-radius: 2px;
|
||||||
|
border-inline-end: none;
|
||||||
|
}
|
||||||
|
`;
|
@ -0,0 +1,8 @@
|
|||||||
|
import { LogsTableViewProps } from 'components/Logs/TableView/types';
|
||||||
|
|
||||||
|
export type InfinityTableProps = {
|
||||||
|
tableViewProps: LogsTableViewProps;
|
||||||
|
infitiyTableProps: {
|
||||||
|
onEndReached: (index: number) => void;
|
||||||
|
};
|
||||||
|
};
|
@ -1,3 +1,11 @@
|
|||||||
import { QueryDataV3 } from 'types/api/widgets/getQuery';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
export type LogsExplorerListProps = { data: QueryDataV3[]; isLoading: boolean };
|
export type LogsExplorerListProps = {
|
||||||
|
isLoading: boolean;
|
||||||
|
currentStagedQueryData: IBuilderQuery | null;
|
||||||
|
logs: ILog[];
|
||||||
|
onEndReached: (index: number) => void;
|
||||||
|
onExpand: (log: ILog) => void;
|
||||||
|
onOpenDetailedView: (log: ILog) => void;
|
||||||
|
};
|
||||||
|
@ -2,38 +2,43 @@ import { Card, Typography } from 'antd';
|
|||||||
// components
|
// components
|
||||||
import ListLogView from 'components/Logs/ListLogView';
|
import ListLogView from 'components/Logs/ListLogView';
|
||||||
import RawLogView from 'components/Logs/RawLogView';
|
import RawLogView from 'components/Logs/RawLogView';
|
||||||
import LogsTableView from 'components/Logs/TableView';
|
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { LogViewMode } from 'container/LogsTable';
|
import ExplorerControlPanel from 'container/ExplorerControlPanel';
|
||||||
import { Container, Heading } from 'container/LogsTable/styles';
|
import { Heading } from 'container/LogsTable/styles';
|
||||||
|
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||||
import { contentStyle } from 'container/Trace/Search/config';
|
import { contentStyle } from 'container/Trace/Search/config';
|
||||||
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import useFontFaceObserver from 'hooks/useFontObserver';
|
import useFontFaceObserver from 'hooks/useFontObserver';
|
||||||
import { memo, useCallback, useMemo, useState } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { Virtuoso } from 'react-virtuoso';
|
import { Virtuoso } from 'react-virtuoso';
|
||||||
// interfaces
|
// interfaces
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import InfinityTableView from './InfinityTableView';
|
||||||
import { LogsExplorerListProps } from './LogsExplorerList.interfaces';
|
import { LogsExplorerListProps } from './LogsExplorerList.interfaces';
|
||||||
|
import { InfinityWrapperStyled } from './styles';
|
||||||
|
import { convertKeysToColumnFields } from './utils';
|
||||||
|
|
||||||
|
function Footer(): JSX.Element {
|
||||||
|
return <Spinner height={20} tip="Getting Logs" />;
|
||||||
|
}
|
||||||
|
|
||||||
function LogsExplorerList({
|
function LogsExplorerList({
|
||||||
data,
|
|
||||||
isLoading,
|
isLoading,
|
||||||
|
currentStagedQueryData,
|
||||||
|
logs,
|
||||||
|
onOpenDetailedView,
|
||||||
|
onEndReached,
|
||||||
|
onExpand,
|
||||||
}: LogsExplorerListProps): JSX.Element {
|
}: LogsExplorerListProps): JSX.Element {
|
||||||
const [viewMode] = useState<LogViewMode>('raw');
|
const { initialDataSource } = useQueryBuilder();
|
||||||
const [linesPerRow] = useState<number>(20);
|
|
||||||
|
|
||||||
const logs: ILog[] = useMemo(() => {
|
const { options, config } = useOptionsMenu({
|
||||||
if (data.length > 0 && data[0].list) {
|
dataSource: initialDataSource || DataSource.METRICS,
|
||||||
const logs: ILog[] = data[0].list.map((item) => ({
|
aggregateOperator:
|
||||||
timestamp: +item.timestamp,
|
currentStagedQueryData?.aggregateOperator || StringOperators.NOOP,
|
||||||
...item.data,
|
});
|
||||||
}));
|
|
||||||
|
|
||||||
return logs;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
useFontFaceObserver(
|
useFontFaceObserver(
|
||||||
[
|
[
|
||||||
@ -42,75 +47,107 @@ function LogsExplorerList({
|
|||||||
weight: '300',
|
weight: '300',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
viewMode === 'raw',
|
options.format === 'raw',
|
||||||
{
|
{
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// TODO: implement here linesPerRow, mode like in useSelectedLogView
|
|
||||||
|
const selectedFields = useMemo(
|
||||||
|
() => convertKeysToColumnFields(options.selectColumns),
|
||||||
|
[options],
|
||||||
|
);
|
||||||
|
|
||||||
const getItemContent = useCallback(
|
const getItemContent = useCallback(
|
||||||
(index: number): JSX.Element => {
|
(_: number, log: ILog): JSX.Element => {
|
||||||
const log = logs[index];
|
if (options.format === 'raw') {
|
||||||
|
|
||||||
if (viewMode === 'raw') {
|
|
||||||
return (
|
return (
|
||||||
<RawLogView
|
<RawLogView
|
||||||
key={log.id}
|
key={log.id}
|
||||||
data={log}
|
data={log}
|
||||||
linesPerRow={linesPerRow}
|
linesPerRow={options.maxLines}
|
||||||
// TODO: write new onClickExpanded logic
|
onClickExpand={onExpand}
|
||||||
onClickExpand={(): void => {}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ListLogView key={log.id} logData={log} />;
|
return (
|
||||||
|
<ListLogView
|
||||||
|
key={log.id}
|
||||||
|
logData={log}
|
||||||
|
selectedFields={selectedFields}
|
||||||
|
onOpenDetailedView={onOpenDetailedView}
|
||||||
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[logs, linesPerRow, viewMode],
|
[
|
||||||
|
options.format,
|
||||||
|
options.maxLines,
|
||||||
|
selectedFields,
|
||||||
|
onOpenDetailedView,
|
||||||
|
onExpand,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderContent = useMemo(() => {
|
const renderContent = useMemo(() => {
|
||||||
if (viewMode === 'table') {
|
const components = isLoading
|
||||||
|
? {
|
||||||
|
Footer,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
if (options.format === 'table') {
|
||||||
return (
|
return (
|
||||||
<LogsTableView
|
<InfinityTableView
|
||||||
logs={logs}
|
tableViewProps={{
|
||||||
// TODO: write new selected logic
|
logs,
|
||||||
fields={[]}
|
fields: selectedFields,
|
||||||
linesPerRow={linesPerRow}
|
linesPerRow: options.maxLines,
|
||||||
// TODO: write new onClickExpanded logic
|
onClickExpand: onExpand,
|
||||||
onClickExpand={(): void => {}}
|
}}
|
||||||
|
infitiyTableProps={{ onEndReached }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card bodyStyle={contentStyle}>
|
<Card style={{ width: '100%' }} bodyStyle={{ ...contentStyle }}>
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
useWindowScroll
|
useWindowScroll
|
||||||
|
data={logs}
|
||||||
|
endReached={onEndReached}
|
||||||
totalCount={logs.length}
|
totalCount={logs.length}
|
||||||
itemContent={getItemContent}
|
itemContent={getItemContent}
|
||||||
|
components={components}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}, [getItemContent, linesPerRow, logs, viewMode]);
|
}, [
|
||||||
|
isLoading,
|
||||||
if (isLoading) {
|
logs,
|
||||||
return <Spinner height={20} tip="Getting Logs" />;
|
options.format,
|
||||||
}
|
options.maxLines,
|
||||||
|
onEndReached,
|
||||||
|
getItemContent,
|
||||||
|
selectedFields,
|
||||||
|
onExpand,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<>
|
||||||
{viewMode !== 'table' && (
|
<ExplorerControlPanel
|
||||||
|
isLoading={isLoading}
|
||||||
|
isShowPageSize={false}
|
||||||
|
optionsMenuConfig={config}
|
||||||
|
/>
|
||||||
|
{options.format !== 'table' && (
|
||||||
<Heading>
|
<Heading>
|
||||||
<Typography.Text>Event</Typography.Text>
|
<Typography.Text>Event</Typography.Text>
|
||||||
</Heading>
|
</Heading>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{logs.length === 0 && <Typography>No logs lines found</Typography>}
|
{logs.length === 0 && <Typography>No logs lines found</Typography>}
|
||||||
|
<InfinityWrapperStyled>{renderContent}</InfinityWrapperStyled>
|
||||||
{renderContent}
|
</>
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
6
frontend/src/container/LogsExplorerList/styles.ts
Normal file
6
frontend/src/container/LogsExplorerList/styles.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const InfinityWrapperStyled = styled.div`
|
||||||
|
min-height: 40rem;
|
||||||
|
display: flex;
|
||||||
|
`;
|
11
frontend/src/container/LogsExplorerList/utils.ts
Normal file
11
frontend/src/container/LogsExplorerList/utils.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { IField } from 'types/api/logs/fields';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
export const convertKeysToColumnFields = (
|
||||||
|
keys: BaseAutocompleteData[],
|
||||||
|
): IField[] =>
|
||||||
|
keys.map((item) => ({
|
||||||
|
dataType: item.dataType as string,
|
||||||
|
name: item.key,
|
||||||
|
type: item.type as string,
|
||||||
|
}));
|
@ -6,8 +6,8 @@ import { memo } from 'react';
|
|||||||
import { LogsExplorerTableProps } from './LogsExplorerTable.interfaces';
|
import { LogsExplorerTableProps } from './LogsExplorerTable.interfaces';
|
||||||
|
|
||||||
function LogsExplorerTable({
|
function LogsExplorerTable({
|
||||||
isLoading,
|
|
||||||
data,
|
data,
|
||||||
|
isLoading,
|
||||||
}: LogsExplorerTableProps): JSX.Element {
|
}: LogsExplorerTableProps): JSX.Element {
|
||||||
const { stagedQuery } = useQueryBuilder();
|
const { stagedQuery } = useQueryBuilder();
|
||||||
|
|
||||||
|
@ -1,50 +1,62 @@
|
|||||||
import { TabsProps } from 'antd';
|
import { TabsProps } from 'antd';
|
||||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { PANEL_TYPES_QUERY } from 'constants/queryBuilderQueryNames';
|
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { DEFAULT_PER_PAGE_VALUE } from 'container/Controls/config';
|
||||||
|
import LogExplorerDetailedView from 'container/LogExplorerDetailedView';
|
||||||
|
import LogsExplorerChart from 'container/LogsExplorerChart';
|
||||||
import LogsExplorerList from 'container/LogsExplorerList';
|
import LogsExplorerList from 'container/LogsExplorerList';
|
||||||
import LogsExplorerTable from 'container/LogsExplorerTable';
|
import LogsExplorerTable from 'container/LogsExplorerTable';
|
||||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||||
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
|
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
|
||||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { memo, useCallback, useEffect, useMemo } from 'react';
|
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||||
import { useSelector } from 'react-redux';
|
import { getPaginationQueryData } from 'lib/newQueryBuilder/getPaginationQueryData';
|
||||||
import { AppState } from 'store/reducers';
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { ILog } from 'types/api/logs/log';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import {
|
||||||
|
IBuilderQuery,
|
||||||
|
OrderByPayload,
|
||||||
|
Query,
|
||||||
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import { TabsStyled } from './LogsExplorerViews.styled';
|
import { TabsStyled } from './LogsExplorerViews.styled';
|
||||||
|
|
||||||
function LogsExplorerViews(): JSX.Element {
|
function LogsExplorerViews(): JSX.Element {
|
||||||
|
const { queryData: pageSize } = useUrlQueryData(
|
||||||
|
queryParamNamesMap.pageSize,
|
||||||
|
DEFAULT_PER_PAGE_VALUE,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Context
|
||||||
const {
|
const {
|
||||||
currentQuery,
|
currentQuery,
|
||||||
stagedQuery,
|
stagedQuery,
|
||||||
panelType,
|
panelType,
|
||||||
isEnabledQuery,
|
|
||||||
updateAllQueriesOperators,
|
updateAllQueriesOperators,
|
||||||
redirectWithQueryBuilderData,
|
redirectWithQueryBuilderData,
|
||||||
} = useQueryBuilder();
|
} = useQueryBuilder();
|
||||||
|
|
||||||
const { selectedTime } = useSelector<AppState, GlobalReducer>(
|
// State
|
||||||
(state) => state.globalTime,
|
const [activeLog, setActiveLog] = useState<ILog | null>(null);
|
||||||
);
|
const [page, setPage] = useState<number>(1);
|
||||||
|
const [logs, setLogs] = useState<ILog[]>([]);
|
||||||
|
const [requestData, setRequestData] = useState<Query | null>(null);
|
||||||
|
|
||||||
const { data, isFetching, isError } = useGetQueryRange(
|
const currentStagedQueryData = useMemo(() => {
|
||||||
{
|
if (!stagedQuery || stagedQuery.builder.queryData.length !== 1) return null;
|
||||||
query: stagedQuery || initialQueriesMap.metrics,
|
|
||||||
graphType: panelType || PANEL_TYPES.LIST,
|
return stagedQuery.builder.queryData[0];
|
||||||
globalSelectedInterval: selectedTime,
|
}, [stagedQuery]);
|
||||||
selectedTime: 'GLOBAL_TIME',
|
|
||||||
params: {
|
const orderByTimestamp: OrderByPayload | null = useMemo(() => {
|
||||||
dataSource: DataSource.LOGS,
|
const timestampOrderBy = currentStagedQueryData?.orderBy.find(
|
||||||
},
|
(item) => item.columnName === 'timestamp',
|
||||||
},
|
);
|
||||||
{
|
|
||||||
queryKey: [REACT_QUERY_KEY.GET_QUERY_RANGE, selectedTime, stagedQuery],
|
return timestampOrderBy || null;
|
||||||
enabled: isEnabledQuery,
|
}, [currentStagedQueryData]);
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const isMultipleQueries = useMemo(
|
const isMultipleQueries = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -62,35 +74,57 @@ function LogsExplorerViews(): JSX.Element {
|
|||||||
return groupByCount > 0;
|
return groupByCount > 0;
|
||||||
}, [currentQuery]);
|
}, [currentQuery]);
|
||||||
|
|
||||||
const currentData = useMemo(
|
const isLimit: boolean = useMemo(() => {
|
||||||
() => data?.payload.data.newResult.data.result || [],
|
if (!currentStagedQueryData) return false;
|
||||||
[data],
|
if (!currentStagedQueryData.limit) return false;
|
||||||
|
|
||||||
|
return logs.length >= currentStagedQueryData.limit;
|
||||||
|
}, [logs.length, currentStagedQueryData]);
|
||||||
|
|
||||||
|
const listChartQuery = useMemo(() => {
|
||||||
|
if (!stagedQuery || !currentStagedQueryData) return null;
|
||||||
|
|
||||||
|
const modifiedQueryData: IBuilderQuery = {
|
||||||
|
...currentStagedQueryData,
|
||||||
|
aggregateOperator: StringOperators.COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
const modifiedQuery: Query = {
|
||||||
|
...stagedQuery,
|
||||||
|
builder: {
|
||||||
|
...stagedQuery.builder,
|
||||||
|
queryData: stagedQuery.builder.queryData.map((item) => ({
|
||||||
|
...item,
|
||||||
|
...modifiedQueryData,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return modifiedQuery;
|
||||||
|
}, [stagedQuery, currentStagedQueryData]);
|
||||||
|
|
||||||
|
const listChartData = useGetExplorerQueryRange(
|
||||||
|
listChartQuery,
|
||||||
|
PANEL_TYPES.TIME_SERIES,
|
||||||
);
|
);
|
||||||
|
|
||||||
const tabsItems: TabsProps['items'] = useMemo(
|
const { data, isFetching, isError } = useGetExplorerQueryRange(
|
||||||
() => [
|
requestData,
|
||||||
{
|
panelType,
|
||||||
label: 'List View',
|
{
|
||||||
key: PANEL_TYPES.LIST,
|
keepPreviousData: true,
|
||||||
disabled: isMultipleQueries || isGroupByExist,
|
enabled: !isLimit,
|
||||||
children: <LogsExplorerList data={currentData} isLoading={isFetching} />,
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'TimeSeries',
|
|
||||||
key: PANEL_TYPES.TIME_SERIES,
|
|
||||||
children: (
|
|
||||||
<TimeSeriesView isLoading={isFetching} data={data} isError={isError} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Table',
|
|
||||||
key: PANEL_TYPES.TABLE,
|
|
||||||
children: <LogsExplorerTable data={currentData} isLoading={isFetching} />,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[isMultipleQueries, isGroupByExist, currentData, isFetching, data, isError],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleSetActiveLog = useCallback((nextActiveLog: ILog) => {
|
||||||
|
setActiveLog(nextActiveLog);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleClearActiveLog = useCallback(() => {
|
||||||
|
setActiveLog(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleChangeView = useCallback(
|
const handleChangeView = useCallback(
|
||||||
(newPanelType: string) => {
|
(newPanelType: string) => {
|
||||||
if (newPanelType === panelType) return;
|
if (newPanelType === panelType) return;
|
||||||
@ -101,7 +135,9 @@ function LogsExplorerViews(): JSX.Element {
|
|||||||
DataSource.LOGS,
|
DataSource.LOGS,
|
||||||
);
|
);
|
||||||
|
|
||||||
redirectWithQueryBuilderData(query, { [PANEL_TYPES_QUERY]: newPanelType });
|
redirectWithQueryBuilderData(query, {
|
||||||
|
[queryParamNamesMap.panelTypes]: newPanelType,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
currentQuery,
|
currentQuery,
|
||||||
@ -111,6 +147,75 @@ function LogsExplorerViews(): JSX.Element {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getRequestData = useCallback(
|
||||||
|
(
|
||||||
|
query: Query | null,
|
||||||
|
params: { page: number; log: ILog | null; pageSize: number },
|
||||||
|
): Query | null => {
|
||||||
|
if (!query) return null;
|
||||||
|
|
||||||
|
const paginateData = getPaginationQueryData({
|
||||||
|
currentStagedQueryData,
|
||||||
|
listItemId: params.log ? params.log.id : null,
|
||||||
|
orderByTimestamp,
|
||||||
|
page: params.page,
|
||||||
|
pageSize: params.pageSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data: Query = {
|
||||||
|
...query,
|
||||||
|
builder: {
|
||||||
|
...query.builder,
|
||||||
|
queryData: query.builder.queryData.map((item) => ({
|
||||||
|
...item,
|
||||||
|
...paginateData,
|
||||||
|
pageSize: params.pageSize,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
[currentStagedQueryData, orderByTimestamp],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleEndReached = useCallback(
|
||||||
|
(index: number) => {
|
||||||
|
if (isLimit) return;
|
||||||
|
|
||||||
|
const lastLog = logs[index];
|
||||||
|
|
||||||
|
const limit = currentStagedQueryData?.limit;
|
||||||
|
|
||||||
|
const nextLogsLenth = logs.length + pageSize;
|
||||||
|
|
||||||
|
const nextPageSize =
|
||||||
|
limit && nextLogsLenth >= limit ? limit - logs.length : pageSize;
|
||||||
|
|
||||||
|
if (!stagedQuery) return;
|
||||||
|
|
||||||
|
const newRequestData = getRequestData(stagedQuery, {
|
||||||
|
page: page + 1,
|
||||||
|
log: orderByTimestamp ? lastLog : null,
|
||||||
|
pageSize: nextPageSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
setPage((prevPage) => prevPage + 1);
|
||||||
|
|
||||||
|
setRequestData(newRequestData);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
isLimit,
|
||||||
|
logs,
|
||||||
|
currentStagedQueryData?.limit,
|
||||||
|
pageSize,
|
||||||
|
stagedQuery,
|
||||||
|
getRequestData,
|
||||||
|
page,
|
||||||
|
orderByTimestamp,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const shouldChangeView = isMultipleQueries || isGroupByExist;
|
const shouldChangeView = isMultipleQueries || isGroupByExist;
|
||||||
|
|
||||||
@ -119,15 +224,115 @@ function LogsExplorerViews(): JSX.Element {
|
|||||||
}
|
}
|
||||||
}, [panelType, isMultipleQueries, isGroupByExist, handleChangeView]);
|
}, [panelType, isMultipleQueries, isGroupByExist, handleChangeView]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const currentData = data?.payload.data.newResult.data.result || [];
|
||||||
|
if (currentData.length > 0 && currentData[0].list) {
|
||||||
|
const currentLogs: ILog[] = currentData[0].list.map((item) => ({
|
||||||
|
...item.data,
|
||||||
|
timestamp: item.timestamp,
|
||||||
|
}));
|
||||||
|
setLogs((prevLogs) => [...prevLogs, ...currentLogs]);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (requestData?.id !== stagedQuery?.id) {
|
||||||
|
const newRequestData = getRequestData(stagedQuery, {
|
||||||
|
page: 1,
|
||||||
|
log: null,
|
||||||
|
pageSize,
|
||||||
|
});
|
||||||
|
setLogs([]);
|
||||||
|
setPage(1);
|
||||||
|
setRequestData(newRequestData);
|
||||||
|
}
|
||||||
|
}, [stagedQuery, requestData, getRequestData, pageSize]);
|
||||||
|
|
||||||
|
const tabsItems: TabsProps['items'] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
label: 'List View',
|
||||||
|
key: PANEL_TYPES.LIST,
|
||||||
|
disabled: isMultipleQueries || isGroupByExist,
|
||||||
|
children: (
|
||||||
|
<LogsExplorerList
|
||||||
|
isLoading={isFetching}
|
||||||
|
currentStagedQueryData={currentStagedQueryData}
|
||||||
|
logs={logs}
|
||||||
|
onOpenDetailedView={handleSetActiveLog}
|
||||||
|
onEndReached={handleEndReached}
|
||||||
|
onExpand={handleSetActiveLog}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'TimeSeries',
|
||||||
|
key: PANEL_TYPES.TIME_SERIES,
|
||||||
|
children: (
|
||||||
|
<TimeSeriesView isLoading={isFetching} data={data} isError={isError} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Table',
|
||||||
|
key: PANEL_TYPES.TABLE,
|
||||||
|
children: (
|
||||||
|
<LogsExplorerTable
|
||||||
|
data={data?.payload.data.newResult.data.result || []}
|
||||||
|
isLoading={isFetching}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
isMultipleQueries,
|
||||||
|
isGroupByExist,
|
||||||
|
isFetching,
|
||||||
|
currentStagedQueryData,
|
||||||
|
logs,
|
||||||
|
handleSetActiveLog,
|
||||||
|
handleEndReached,
|
||||||
|
data,
|
||||||
|
isError,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const chartData = useMemo(() => {
|
||||||
|
if (!stagedQuery) return [];
|
||||||
|
|
||||||
|
if (panelType === PANEL_TYPES.LIST) {
|
||||||
|
if (
|
||||||
|
listChartData &&
|
||||||
|
listChartData.data &&
|
||||||
|
listChartData.data.payload.data.result.length > 0
|
||||||
|
) {
|
||||||
|
return listChartData.data.payload.data.result;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || data.payload.data.result.length === 0) return [];
|
||||||
|
|
||||||
|
const isGroupByExist = stagedQuery.builder.queryData.some(
|
||||||
|
(queryData) => queryData.groupBy.length > 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
return isGroupByExist
|
||||||
|
? data.payload.data.result
|
||||||
|
: [data.payload.data.result[0]];
|
||||||
|
}, [stagedQuery, data, panelType, listChartData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
|
<LogsExplorerChart isLoading={isFetching} data={chartData} />
|
||||||
<TabsStyled
|
<TabsStyled
|
||||||
items={tabsItems}
|
items={tabsItems}
|
||||||
defaultActiveKey={panelType || PANEL_TYPES.LIST}
|
defaultActiveKey={panelType || PANEL_TYPES.LIST}
|
||||||
activeKey={panelType || PANEL_TYPES.LIST}
|
activeKey={panelType || PANEL_TYPES.LIST}
|
||||||
onChange={handleChangeView}
|
onChange={handleChangeView}
|
||||||
|
destroyInactiveTabPane
|
||||||
/>
|
/>
|
||||||
</div>
|
<LogExplorerDetailedView log={activeLog} onClose={handleClearActiveLog} />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,10 +7,11 @@ import Spinner from 'components/Spinner';
|
|||||||
import { contentStyle } from 'container/Trace/Search/config';
|
import { contentStyle } from 'container/Trace/Search/config';
|
||||||
import useFontFaceObserver from 'hooks/useFontObserver';
|
import useFontFaceObserver from 'hooks/useFontObserver';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { Virtuoso } from 'react-virtuoso';
|
import { Virtuoso } from 'react-virtuoso';
|
||||||
// interfaces
|
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
// interfaces
|
||||||
|
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
|
|
||||||
@ -28,6 +29,8 @@ type LogsTableProps = {
|
|||||||
function LogsTable(props: LogsTableProps): JSX.Element {
|
function LogsTable(props: LogsTableProps): JSX.Element {
|
||||||
const { viewMode, onClickExpand, linesPerRow } = props;
|
const { viewMode, onClickExpand, linesPerRow } = props;
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
useFontFaceObserver(
|
useFontFaceObserver(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -58,6 +61,16 @@ function LogsTable(props: LogsTableProps): JSX.Element {
|
|||||||
liveTail,
|
liveTail,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const handleOpenDetailedView = useCallback(
|
||||||
|
(logData: ILog) => {
|
||||||
|
dispatch({
|
||||||
|
type: SET_DETAILED_LOG_DATA,
|
||||||
|
payload: logData,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
const getItemContent = useCallback(
|
const getItemContent = useCallback(
|
||||||
(index: number): JSX.Element => {
|
(index: number): JSX.Element => {
|
||||||
const log = logs[index];
|
const log = logs[index];
|
||||||
@ -73,9 +86,23 @@ function LogsTable(props: LogsTableProps): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ListLogView key={log.id} logData={log} />;
|
return (
|
||||||
|
<ListLogView
|
||||||
|
key={log.id}
|
||||||
|
logData={log}
|
||||||
|
selectedFields={selected}
|
||||||
|
onOpenDetailedView={handleOpenDetailedView}
|
||||||
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[logs, linesPerRow, viewMode, onClickExpand],
|
[
|
||||||
|
logs,
|
||||||
|
viewMode,
|
||||||
|
selected,
|
||||||
|
handleOpenDetailedView,
|
||||||
|
linesPerRow,
|
||||||
|
onClickExpand,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderContent = useMemo(() => {
|
const renderContent = useMemo(() => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
|
||||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||||
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
|
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
@ -47,7 +47,7 @@ function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element {
|
|||||||
history.push(
|
history.push(
|
||||||
`${history.location.pathname}/new?graphType=${name}&widgetId=${
|
`${history.location.pathname}/new?graphType=${name}&widgetId=${
|
||||||
emptyLayout.i
|
emptyLayout.i
|
||||||
}&${COMPOSITE_QUERY}=${encodeURIComponent(
|
}&${queryParamNamesMap.compositeQuery}=${encodeURIComponent(
|
||||||
JSON.stringify(initialQueriesMap.metrics),
|
JSON.stringify(initialQueriesMap.metrics),
|
||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
|
@ -18,9 +18,9 @@ function FormatField({ config }: FormatFieldProps): JSX.Element | null {
|
|||||||
value={config.value}
|
value={config.value}
|
||||||
onChange={config.onChange}
|
onChange={config.onChange}
|
||||||
>
|
>
|
||||||
<RadioButton value="row">{t('options_menu.row')}</RadioButton>
|
<RadioButton value="raw">{t('options_menu.row')}</RadioButton>
|
||||||
<RadioButton value="default">{t('options_menu.default')}</RadioButton>
|
<RadioButton value="list">{t('options_menu.default')}</RadioButton>
|
||||||
<RadioButton value="column">{t('options_menu.column')}</RadioButton>
|
<RadioButton value="table">{t('options_menu.column')}</RadioButton>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</FormatFieldWrapper>
|
</FormatFieldWrapper>
|
||||||
);
|
);
|
||||||
|
@ -4,6 +4,6 @@ export const URL_OPTIONS = 'options';
|
|||||||
|
|
||||||
export const defaultOptionsQuery: OptionsQuery = {
|
export const defaultOptionsQuery: OptionsQuery = {
|
||||||
selectColumns: [],
|
selectColumns: [],
|
||||||
maxLines: 0,
|
maxLines: 2,
|
||||||
format: 'default',
|
format: 'list',
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { InputNumberProps, RadioProps, SelectProps } from 'antd';
|
import { InputNumberProps, RadioProps, SelectProps } from 'antd';
|
||||||
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
export interface OptionsQuery {
|
export interface OptionsQuery {
|
||||||
selectColumns: BaseAutocompleteData[];
|
selectColumns: BaseAutocompleteData[];
|
||||||
maxLines: number;
|
maxLines: number;
|
||||||
format: 'default' | 'row' | 'column';
|
format: LogViewMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InitialOptions
|
export interface InitialOptions
|
||||||
|
@ -35,16 +35,17 @@ const useOptionsMenu = ({
|
|||||||
query: optionsQuery,
|
query: optionsQuery,
|
||||||
queryData: optionsQueryData,
|
queryData: optionsQueryData,
|
||||||
redirectWithQuery: redirectWithOptionsData,
|
redirectWithQuery: redirectWithOptionsData,
|
||||||
} = useUrlQueryData<OptionsQuery>(URL_OPTIONS);
|
} = useUrlQueryData<OptionsQuery>(URL_OPTIONS, defaultOptionsQuery);
|
||||||
|
|
||||||
const { data, isFetched, isLoading } = useQuery(
|
const { data, isFetched, isLoading } = useQuery(
|
||||||
[QueryBuilderKeys.GET_ATTRIBUTE_KEY],
|
[QueryBuilderKeys.GET_ATTRIBUTE_KEY, dataSource, aggregateOperator],
|
||||||
async () =>
|
async () =>
|
||||||
getAggregateKeys({
|
getAggregateKeys({
|
||||||
searchText: '',
|
searchText: '',
|
||||||
dataSource,
|
dataSource,
|
||||||
aggregateOperator,
|
aggregateOperator,
|
||||||
aggregateAttribute: '',
|
aggregateAttribute: '',
|
||||||
|
tagType: null,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -86,11 +87,16 @@ const useOptionsMenu = ({
|
|||||||
}, [] as BaseAutocompleteData[]);
|
}, [] as BaseAutocompleteData[]);
|
||||||
|
|
||||||
redirectWithOptionsData({
|
redirectWithOptionsData({
|
||||||
...defaultOptionsQuery,
|
...optionsQueryData,
|
||||||
selectColumns: newSelectedColumns,
|
selectColumns: newSelectedColumns,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[attributeKeys, selectedColumnKeys, redirectWithOptionsData],
|
[
|
||||||
|
selectedColumnKeys,
|
||||||
|
redirectWithOptionsData,
|
||||||
|
optionsQueryData,
|
||||||
|
attributeKeys,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleRemoveSelectedColumn = useCallback(
|
const handleRemoveSelectedColumn = useCallback(
|
||||||
@ -116,21 +122,21 @@ const useOptionsMenu = ({
|
|||||||
const handleFormatChange = useCallback(
|
const handleFormatChange = useCallback(
|
||||||
(event: RadioChangeEvent) => {
|
(event: RadioChangeEvent) => {
|
||||||
redirectWithOptionsData({
|
redirectWithOptionsData({
|
||||||
...defaultOptionsQuery,
|
...optionsQueryData,
|
||||||
format: event.target.value,
|
format: event.target.value,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[redirectWithOptionsData],
|
[optionsQueryData, redirectWithOptionsData],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleMaxLinesChange = useCallback(
|
const handleMaxLinesChange = useCallback(
|
||||||
(value: string | number | null) => {
|
(value: string | number | null) => {
|
||||||
redirectWithOptionsData({
|
redirectWithOptionsData({
|
||||||
...defaultOptionsQuery,
|
...optionsQueryData,
|
||||||
maxLines: value as number,
|
maxLines: value as number,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[redirectWithOptionsData],
|
[optionsQueryData, redirectWithOptionsData],
|
||||||
);
|
);
|
||||||
|
|
||||||
const optionsMenuConfig: Required<OptionsMenuConfig> = useMemo(
|
const optionsMenuConfig: Required<OptionsMenuConfig> = useMemo(
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
export type PageSizeSelectProps = {
|
||||||
|
isLoading: boolean;
|
||||||
|
isShow: boolean;
|
||||||
|
};
|
51
frontend/src/container/PageSizeSelect/index.tsx
Normal file
51
frontend/src/container/PageSizeSelect/index.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Col, Row, Select } from 'antd';
|
||||||
|
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
||||||
|
import {
|
||||||
|
defaultSelectStyle,
|
||||||
|
ITEMS_PER_PAGE_OPTIONS,
|
||||||
|
} from 'container/Controls/config';
|
||||||
|
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { PageSizeSelectProps } from './PageSizeSelect.interfaces';
|
||||||
|
|
||||||
|
function PageSizeSelect({
|
||||||
|
isLoading,
|
||||||
|
isShow,
|
||||||
|
}: PageSizeSelectProps): JSX.Element | null {
|
||||||
|
const { redirectWithQuery, queryData: pageSize } = useUrlQueryData<number>(
|
||||||
|
queryParamNamesMap.pageSize,
|
||||||
|
ITEMS_PER_PAGE_OPTIONS[0],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangePageSize = useCallback(
|
||||||
|
(value: number) => {
|
||||||
|
redirectWithQuery(value);
|
||||||
|
},
|
||||||
|
[redirectWithQuery],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isShow) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<Select
|
||||||
|
style={defaultSelectStyle}
|
||||||
|
loading={isLoading}
|
||||||
|
value={pageSize}
|
||||||
|
onChange={handleChangePageSize}
|
||||||
|
>
|
||||||
|
{ITEMS_PER_PAGE_OPTIONS.map((count) => (
|
||||||
|
<Select.Option
|
||||||
|
key={count}
|
||||||
|
value={count}
|
||||||
|
>{`${count} / page`}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PageSizeSelect;
|
@ -1,5 +1,6 @@
|
|||||||
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
|
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
export type QueryBuilderConfig =
|
export type QueryBuilderConfig =
|
||||||
@ -13,4 +14,5 @@ export type QueryBuilderProps = {
|
|||||||
config?: QueryBuilderConfig;
|
config?: QueryBuilderConfig;
|
||||||
panelType: ITEMS;
|
panelType: ITEMS;
|
||||||
actions?: ReactNode;
|
actions?: ReactNode;
|
||||||
|
inactiveFilters?: Partial<Record<keyof IBuilderQuery, boolean>>;
|
||||||
};
|
};
|
||||||
|
@ -17,6 +17,7 @@ export const QueryBuilder = memo(function QueryBuilder({
|
|||||||
config,
|
config,
|
||||||
panelType: newPanelType,
|
panelType: newPanelType,
|
||||||
actions,
|
actions,
|
||||||
|
inactiveFilters = {},
|
||||||
}: QueryBuilderProps): JSX.Element {
|
}: QueryBuilderProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
currentQuery,
|
currentQuery,
|
||||||
@ -74,6 +75,7 @@ export const QueryBuilder = memo(function QueryBuilder({
|
|||||||
isAvailableToDisable={isAvailableToDisableQuery}
|
isAvailableToDisable={isAvailableToDisableQuery}
|
||||||
queryVariant={config?.queryVariant || 'dropdown'}
|
queryVariant={config?.queryVariant || 'dropdown'}
|
||||||
query={query}
|
query={query}
|
||||||
|
inactiveFilters={inactiveFilters}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
export type QueryProps = {
|
export type QueryProps = {
|
||||||
@ -5,4 +6,4 @@ export type QueryProps = {
|
|||||||
isAvailableToDisable: boolean;
|
isAvailableToDisable: boolean;
|
||||||
query: IBuilderQuery;
|
query: IBuilderQuery;
|
||||||
queryVariant: 'static' | 'dropdown';
|
queryVariant: 'static' | 'dropdown';
|
||||||
};
|
} & Pick<QueryBuilderProps, 'inactiveFilters'>;
|
||||||
|
@ -35,6 +35,7 @@ export const Query = memo(function Query({
|
|||||||
isAvailableToDisable,
|
isAvailableToDisable,
|
||||||
queryVariant,
|
queryVariant,
|
||||||
query,
|
query,
|
||||||
|
inactiveFilters,
|
||||||
}: QueryProps): JSX.Element {
|
}: QueryProps): JSX.Element {
|
||||||
const { panelType } = useQueryBuilder();
|
const { panelType } = useQueryBuilder();
|
||||||
const {
|
const {
|
||||||
@ -47,7 +48,7 @@ export const Query = memo(function Query({
|
|||||||
handleChangeQueryData,
|
handleChangeQueryData,
|
||||||
handleChangeOperator,
|
handleChangeOperator,
|
||||||
handleDeleteQuery,
|
handleDeleteQuery,
|
||||||
} = useQueryOperations({ index, query });
|
} = useQueryOperations({ index, query, inactiveFilters });
|
||||||
|
|
||||||
const handleChangeAggregateEvery = useCallback(
|
const handleChangeAggregateEvery = useCallback(
|
||||||
(value: IBuilderQuery['stepInterval']) => {
|
(value: IBuilderQuery['stepInterval']) => {
|
||||||
@ -109,6 +110,24 @@ export const Query = memo(function Query({
|
|||||||
[handleChangeQueryData],
|
[handleChangeQueryData],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderAggregateEveryFilter = useCallback(
|
||||||
|
(): JSX.Element | null =>
|
||||||
|
!inactiveFilters?.stepInterval ? (
|
||||||
|
<Row gutter={[11, 5]}>
|
||||||
|
<Col flex="5.93rem">
|
||||||
|
<FilterLabel label="Aggregate Every" />
|
||||||
|
</Col>
|
||||||
|
<Col flex="1 1 6rem">
|
||||||
|
<AggregateEveryFilter
|
||||||
|
query={query}
|
||||||
|
onChange={handleChangeAggregateEvery}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
) : null,
|
||||||
|
[inactiveFilters?.stepInterval, query, handleChangeAggregateEvery],
|
||||||
|
);
|
||||||
|
|
||||||
const renderAdditionalFilters = useCallback((): ReactNode => {
|
const renderAdditionalFilters = useCallback((): ReactNode => {
|
||||||
switch (panelType) {
|
switch (panelType) {
|
||||||
case PANEL_TYPES.TIME_SERIES: {
|
case PANEL_TYPES.TIME_SERIES: {
|
||||||
@ -149,19 +168,7 @@ export const Query = memo(function Query({
|
|||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Col span={11}>
|
<Col span={11}>{renderAggregateEveryFilter()}</Col>
|
||||||
<Row gutter={[11, 5]}>
|
|
||||||
<Col flex="5.93rem">
|
|
||||||
<FilterLabel label="Aggregate Every" />
|
|
||||||
</Col>
|
|
||||||
<Col flex="1 1 6rem">
|
|
||||||
<AggregateEveryFilter
|
|
||||||
query={query}
|
|
||||||
onChange={handleChangeAggregateEvery}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -179,19 +186,7 @@ export const Query = memo(function Query({
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={11}>
|
<Col span={11}>{renderAggregateEveryFilter()}</Col>
|
||||||
<Row gutter={[11, 5]}>
|
|
||||||
<Col flex="5.93rem">
|
|
||||||
<FilterLabel label="Aggregate Every" />
|
|
||||||
</Col>
|
|
||||||
<Col flex="1 1 6rem">
|
|
||||||
<AggregateEveryFilter
|
|
||||||
query={query}
|
|
||||||
onChange={handleChangeAggregateEvery}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -230,21 +225,7 @@ export const Query = memo(function Query({
|
|||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
{panelType !== PANEL_TYPES.LIST && (
|
<Col span={11}>{renderAggregateEveryFilter()}</Col>
|
||||||
<Col span={11}>
|
|
||||||
<Row gutter={[11, 5]}>
|
|
||||||
<Col flex="5.93rem">
|
|
||||||
<FilterLabel label="Aggregate Every" />
|
|
||||||
</Col>
|
|
||||||
<Col flex="1 1 6rem">
|
|
||||||
<AggregateEveryFilter
|
|
||||||
query={query}
|
|
||||||
onChange={handleChangeAggregateEvery}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -253,10 +234,10 @@ export const Query = memo(function Query({
|
|||||||
panelType,
|
panelType,
|
||||||
query,
|
query,
|
||||||
isMetricsDataSource,
|
isMetricsDataSource,
|
||||||
handleChangeAggregateEvery,
|
|
||||||
handleChangeHavingFilter,
|
handleChangeHavingFilter,
|
||||||
handleChangeLimit,
|
handleChangeLimit,
|
||||||
handleChangeOrderByKeys,
|
handleChangeOrderByKeys,
|
||||||
|
renderAggregateEveryFilter,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -27,7 +27,7 @@ export function OrderByFilter({
|
|||||||
}: OrderByFilterProps): JSX.Element {
|
}: OrderByFilterProps): JSX.Element {
|
||||||
const [searchText, setSearchText] = useState<string>('');
|
const [searchText, setSearchText] = useState<string>('');
|
||||||
const [selectedValue, setSelectedValue] = useState<IOption[]>(
|
const [selectedValue, setSelectedValue] = useState<IOption[]>(
|
||||||
transformToOrderByStringValues(query.orderBy) || [],
|
transformToOrderByStringValues(query.orderBy),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, isFetching } = useQuery(
|
const { data, isFetching } = useQuery(
|
||||||
|
@ -8,11 +8,25 @@ export const orderByValueDelimiter = '|';
|
|||||||
|
|
||||||
export const transformToOrderByStringValues = (
|
export const transformToOrderByStringValues = (
|
||||||
orderBy: OrderByPayload[],
|
orderBy: OrderByPayload[],
|
||||||
): IOption[] =>
|
): IOption[] => {
|
||||||
orderBy.map((item) => ({
|
const prepareSelectedValue: IOption[] = orderBy.reduce<IOption[]>(
|
||||||
label: `${item.columnName} ${item.order}`,
|
(acc, item) => {
|
||||||
value: `${item.columnName}${orderByValueDelimiter}${item.order}`,
|
if (item.columnName === '#SIGNOZ_VALUE') return acc;
|
||||||
}));
|
|
||||||
|
const option: IOption = {
|
||||||
|
label: `${item.columnName} ${item.order}`,
|
||||||
|
value: `${item.columnName}${orderByValueDelimiter}${item.order}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
acc.push(option);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return prepareSelectedValue;
|
||||||
|
};
|
||||||
|
|
||||||
export function mapLabelValuePairs(
|
export function mapLabelValuePairs(
|
||||||
arr: BaseAutocompleteData[],
|
arr: BaseAutocompleteData[],
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
|
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
@ -7,7 +7,7 @@ export const useGetCompositeQueryParam = (): Query | null => {
|
|||||||
const urlQuery = useUrlQuery();
|
const urlQuery = useUrlQuery();
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const compositeQuery = urlQuery.get(COMPOSITE_QUERY);
|
const compositeQuery = urlQuery.get(queryParamNamesMap.compositeQuery);
|
||||||
let parsedCompositeQuery: Query | null = null;
|
let parsedCompositeQuery: Query | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
58
frontend/src/hooks/queryBuilder/useGetExplorerQueryRange.ts
Normal file
58
frontend/src/hooks/queryBuilder/useGetExplorerQueryRange.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
|
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { UseQueryOptions, UseQueryResult } from 'react-query';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { SuccessResponse } from 'types/api';
|
||||||
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
|
import { useGetQueryRange } from './useGetQueryRange';
|
||||||
|
import { useQueryBuilder } from './useQueryBuilder';
|
||||||
|
|
||||||
|
export const useGetExplorerQueryRange = (
|
||||||
|
requestData: Query | null,
|
||||||
|
panelType: GRAPH_TYPES | null,
|
||||||
|
options?: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>,
|
||||||
|
): UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error> => {
|
||||||
|
const { isEnabledQuery } = useQueryBuilder();
|
||||||
|
const { selectedTime: globalSelectedInterval } = useSelector<
|
||||||
|
AppState,
|
||||||
|
GlobalReducer
|
||||||
|
>((state) => state.globalTime);
|
||||||
|
|
||||||
|
const key = useMemo(
|
||||||
|
() =>
|
||||||
|
typeof options?.queryKey === 'string'
|
||||||
|
? options?.queryKey
|
||||||
|
: REACT_QUERY_KEY.GET_QUERY_RANGE,
|
||||||
|
[options?.queryKey],
|
||||||
|
);
|
||||||
|
|
||||||
|
const isEnabled = useMemo(() => {
|
||||||
|
if (!options) return isEnabledQuery;
|
||||||
|
if (typeof options.enabled === 'boolean') {
|
||||||
|
return isEnabledQuery && options.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isEnabledQuery;
|
||||||
|
}, [options, isEnabledQuery]);
|
||||||
|
|
||||||
|
return useGetQueryRange(
|
||||||
|
{
|
||||||
|
graphType: panelType || PANEL_TYPES.LIST,
|
||||||
|
selectedTime: 'GLOBAL_TIME',
|
||||||
|
globalSelectedInterval,
|
||||||
|
query: requestData || initialQueriesMap.metrics,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
retry: false,
|
||||||
|
queryKey: [key, globalSelectedInterval, requestData],
|
||||||
|
enabled: isEnabled,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { PANEL_TYPES_QUERY } from 'constants/queryBuilderQueryNames';
|
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
||||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@ -9,7 +9,7 @@ export const useGetPanelTypesQueryParam = <T extends GRAPH_TYPES | undefined>(
|
|||||||
const urlQuery = useUrlQuery();
|
const urlQuery = useUrlQuery();
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const panelTypeQuery = urlQuery.get(PANEL_TYPES_QUERY);
|
const panelTypeQuery = urlQuery.get(queryParamNamesMap.panelTypes);
|
||||||
|
|
||||||
return panelTypeQuery ? JSON.parse(panelTypeQuery) : defaultPanelType;
|
return panelTypeQuery ? JSON.parse(panelTypeQuery) : defaultPanelType;
|
||||||
}, [urlQuery, defaultPanelType]);
|
}, [urlQuery, defaultPanelType]);
|
||||||
|
@ -17,7 +17,11 @@ import {
|
|||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
import { SelectOption } from 'types/common/select';
|
import { SelectOption } from 'types/common/select';
|
||||||
|
|
||||||
export const useQueryOperations: UseQueryOperations = ({ query, index }) => {
|
export const useQueryOperations: UseQueryOperations = ({
|
||||||
|
query,
|
||||||
|
index,
|
||||||
|
inactiveFilters,
|
||||||
|
}) => {
|
||||||
const {
|
const {
|
||||||
handleSetQueryData,
|
handleSetQueryData,
|
||||||
removeQueryBuilderEntityByIndex,
|
removeQueryBuilderEntityByIndex,
|
||||||
@ -58,15 +62,23 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => {
|
|||||||
|
|
||||||
const getNewListOfAdditionalFilters = useCallback(
|
const getNewListOfAdditionalFilters = useCallback(
|
||||||
(dataSource: DataSource): string[] => {
|
(dataSource: DataSource): string[] => {
|
||||||
const listOfFilters = mapOfFilters[dataSource].map((item) => item.text);
|
const result: string[] = mapOfFilters[dataSource].reduce<string[]>(
|
||||||
|
(acc, item) => {
|
||||||
|
if (inactiveFilters && inactiveFilters[item.field]) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
if (panelType === PANEL_TYPES.LIST) {
|
acc.push(item.text);
|
||||||
return listOfFilters.filter((filter) => filter !== 'Aggregation interval');
|
|
||||||
}
|
|
||||||
|
|
||||||
return listOfFilters;
|
return acc;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
},
|
},
|
||||||
[panelType],
|
|
||||||
|
[inactiveFilters],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChangeAggregatorAttribute = useCallback(
|
const handleChangeAggregatorAttribute = useCallback(
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
import { ChartData } from 'chart.js';
|
|
||||||
import getLabelName from 'lib/getLabelName';
|
|
||||||
import { QueryData } from 'types/api/widgets/getQuery';
|
|
||||||
|
|
||||||
import { colors } from '../getRandomColor';
|
|
||||||
|
|
||||||
export const getExplorerChartData = (
|
|
||||||
queryData: QueryData[],
|
|
||||||
): ChartData<'bar'> => {
|
|
||||||
const uniqueTimeLabels = new Set<number>();
|
|
||||||
|
|
||||||
const sortedData = [...queryData].sort((a, b) => {
|
|
||||||
if (a.queryName < b.queryName) return -1;
|
|
||||||
if (a.queryName > b.queryName) return 1;
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
const modifiedData: { label: string }[] = sortedData.map((result) => {
|
|
||||||
const { metric, queryName, legend } = result;
|
|
||||||
result.values.forEach((value) => {
|
|
||||||
uniqueTimeLabels.add(value[0] * 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: getLabelName(metric, queryName || '', legend || ''),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const labels = Array.from(uniqueTimeLabels)
|
|
||||||
.sort((a, b) => a - b)
|
|
||||||
.map((value) => new Date(value));
|
|
||||||
|
|
||||||
const allLabels = modifiedData.map((e) => e.label);
|
|
||||||
|
|
||||||
const data: ChartData<'bar'> = {
|
|
||||||
labels,
|
|
||||||
datasets: queryData.map((result, index) => ({
|
|
||||||
label: allLabels[index],
|
|
||||||
data: result.values.map((item) => parseFloat(item[1])),
|
|
||||||
backgroundColor: colors[index % colors.length] || 'red',
|
|
||||||
borderColor: colors[index % colors.length] || 'red',
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
@ -1,11 +1,14 @@
|
|||||||
import { ChartData } from 'chart.js';
|
import { ChartData, ChartDataset } from 'chart.js';
|
||||||
import getLabelName from 'lib/getLabelName';
|
import getLabelName from 'lib/getLabelName';
|
||||||
import { QueryData } from 'types/api/widgets/getQuery';
|
import { QueryData } from 'types/api/widgets/getQuery';
|
||||||
|
|
||||||
import convertIntoEpoc from './covertIntoEpoc';
|
import convertIntoEpoc from './covertIntoEpoc';
|
||||||
import { colors } from './getRandomColor';
|
import { colors } from './getRandomColor';
|
||||||
|
|
||||||
const getChartData = ({ queryData }: GetChartDataProps): ChartData => {
|
const getChartData = ({
|
||||||
|
queryData,
|
||||||
|
createDataset,
|
||||||
|
}: GetChartDataProps): ChartData => {
|
||||||
const uniqueTimeLabels = new Set<number>();
|
const uniqueTimeLabels = new Set<number>();
|
||||||
queryData.forEach((data) => {
|
queryData.forEach((data) => {
|
||||||
data.queryData.forEach((query) => {
|
data.queryData.forEach((query) => {
|
||||||
@ -60,28 +63,39 @@ const getChartData = ({ queryData }: GetChartDataProps): ChartData => {
|
|||||||
.reduce((a, b) => [...a, ...b], []);
|
.reduce((a, b) => [...a, ...b], []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
datasets: alldata.map((e, index) => ({
|
datasets: alldata.map((e, index) => {
|
||||||
data: e,
|
const datasetBaseConfig = {
|
||||||
label: allLabels[index],
|
label: allLabels[index],
|
||||||
borderWidth: 1.5,
|
borderColor: colors[index % colors.length] || 'red',
|
||||||
spanGaps: true,
|
data: e,
|
||||||
animations: false,
|
borderWidth: 1.5,
|
||||||
borderColor: colors[index % colors.length] || 'red',
|
spanGaps: true,
|
||||||
showLine: true,
|
animations: false,
|
||||||
pointRadius: 0,
|
showLine: true,
|
||||||
})),
|
pointRadius: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
return createDataset
|
||||||
|
? createDataset(e, index, allLabels)
|
||||||
|
: datasetBaseConfig;
|
||||||
|
}),
|
||||||
labels: response
|
labels: response
|
||||||
.map((e) => e.map((e) => e.first))
|
.map((e) => e.map((e) => e.first))
|
||||||
.reduce((a, b) => [...a, ...b], [])[0],
|
.reduce((a, b) => [...a, ...b], [])[0],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
interface GetChartDataProps {
|
export interface GetChartDataProps {
|
||||||
queryData: {
|
queryData: {
|
||||||
query?: string;
|
query?: string;
|
||||||
legend?: string;
|
legend?: string;
|
||||||
queryData: QueryData[];
|
queryData: QueryData[];
|
||||||
}[];
|
}[];
|
||||||
|
createDataset?: (
|
||||||
|
element: (number | null)[],
|
||||||
|
index: number,
|
||||||
|
allLabels: string[],
|
||||||
|
) => ChartDataset;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default getChartData;
|
export default getChartData;
|
||||||
|
76
frontend/src/lib/newQueryBuilder/getPaginationQueryData.ts
Normal file
76
frontend/src/lib/newQueryBuilder/getPaginationQueryData.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { initialFilters } from 'constants/queryBuilder';
|
||||||
|
import { FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
||||||
|
import {
|
||||||
|
IBuilderQuery,
|
||||||
|
OrderByPayload,
|
||||||
|
TagFilter,
|
||||||
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
type SetupPaginationQueryDataParams = {
|
||||||
|
currentStagedQueryData: IBuilderQuery | null;
|
||||||
|
listItemId: string | null;
|
||||||
|
orderByTimestamp: OrderByPayload | null;
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SetupPaginationQueryData = (
|
||||||
|
params: SetupPaginationQueryDataParams,
|
||||||
|
) => Pick<IBuilderQuery, 'filters' | 'offset'>;
|
||||||
|
|
||||||
|
export const getPaginationQueryData: SetupPaginationQueryData = ({
|
||||||
|
currentStagedQueryData,
|
||||||
|
listItemId,
|
||||||
|
orderByTimestamp,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
}) => {
|
||||||
|
if (!currentStagedQueryData) {
|
||||||
|
return { limit: null, filters: initialFilters };
|
||||||
|
}
|
||||||
|
|
||||||
|
const filters = currentStagedQueryData.filters || initialFilters;
|
||||||
|
const offset = (page - 1) * pageSize;
|
||||||
|
|
||||||
|
const queryProps =
|
||||||
|
(orderByTimestamp && currentStagedQueryData.orderBy.length > 1) ||
|
||||||
|
!orderByTimestamp
|
||||||
|
? {
|
||||||
|
offset,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
const updatedFilters: TagFilter = {
|
||||||
|
...filters,
|
||||||
|
items: filters.items.filter((item) => item.key?.key !== 'id'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const tagFilters: TagFilter = {
|
||||||
|
...filters,
|
||||||
|
items:
|
||||||
|
listItemId && orderByTimestamp
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: uuid(),
|
||||||
|
key: {
|
||||||
|
key: 'id',
|
||||||
|
type: null,
|
||||||
|
dataType: 'string',
|
||||||
|
isColumn: true,
|
||||||
|
},
|
||||||
|
op: orderByTimestamp.order === FILTERS.ASC ? '>' : '<',
|
||||||
|
value: listItemId,
|
||||||
|
},
|
||||||
|
...updatedFilters.items,
|
||||||
|
]
|
||||||
|
: updatedFilters.items,
|
||||||
|
};
|
||||||
|
|
||||||
|
const chunkOfQueryData: Partial<IBuilderQuery> = {
|
||||||
|
filters: orderByTimestamp ? tagFilters : updatedFilters,
|
||||||
|
...queryProps,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...currentStagedQueryData, ...chunkOfQueryData };
|
||||||
|
};
|
@ -39,6 +39,7 @@ type CreateTableDataFromQuery = (
|
|||||||
type FillColumnData = (
|
type FillColumnData = (
|
||||||
queryTableData: QueryDataV3[],
|
queryTableData: QueryDataV3[],
|
||||||
dynamicColumns: DynamicColumns,
|
dynamicColumns: DynamicColumns,
|
||||||
|
query: Query,
|
||||||
) => { filledDynamicColumns: DynamicColumns; rowsLength: number };
|
) => { filledDynamicColumns: DynamicColumns; rowsLength: number };
|
||||||
|
|
||||||
type GetDynamicColumns = (
|
type GetDynamicColumns = (
|
||||||
@ -177,7 +178,8 @@ const fillEmptyRowCells = (
|
|||||||
const fillDataFromSeria = (
|
const fillDataFromSeria = (
|
||||||
seria: SeriesItem,
|
seria: SeriesItem,
|
||||||
columns: DynamicColumns,
|
columns: DynamicColumns,
|
||||||
currentQueryName: string,
|
queryName: string,
|
||||||
|
operator: string,
|
||||||
): void => {
|
): void => {
|
||||||
const labelEntries = Object.entries(seria.labels);
|
const labelEntries = Object.entries(seria.labels);
|
||||||
|
|
||||||
@ -193,7 +195,13 @@ const fillDataFromSeria = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentQueryName === column.key) {
|
if (isFormula(queryName) && queryName === column.key) {
|
||||||
|
column.data.push(parseFloat(value.value).toFixed(2));
|
||||||
|
unusedColumnsKeys.delete(column.key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isFormula(queryName) && operator === column.key) {
|
||||||
column.data.push(parseFloat(value.value).toFixed(2));
|
column.data.push(parseFloat(value.value).toFixed(2));
|
||||||
unusedColumnsKeys.delete(column.key);
|
unusedColumnsKeys.delete(column.key);
|
||||||
return;
|
return;
|
||||||
@ -230,20 +238,25 @@ const fillDataFromList = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const fillColumnsData: FillColumnData = (queryTableData, cols) => {
|
const fillColumnsData: FillColumnData = (queryTableData, cols, query) => {
|
||||||
const fields = cols.filter((item) => item.type === 'field');
|
const fields = cols.filter((item) => item.type === 'field');
|
||||||
const operators = cols.filter((item) => item.type === 'operator');
|
const operators = cols.filter((item) => item.type === 'operator');
|
||||||
const resultColumns = [...fields, ...operators];
|
const resultColumns = [...fields, ...operators];
|
||||||
|
|
||||||
queryTableData.forEach((currentQuery) => {
|
queryTableData.forEach((currentQuery) => {
|
||||||
// const currentOperator = getQueryOperator(
|
|
||||||
// query.builder.queryData,
|
|
||||||
// currentQuery.queryName,
|
|
||||||
// );
|
|
||||||
|
|
||||||
if (currentQuery.series) {
|
if (currentQuery.series) {
|
||||||
currentQuery.series.forEach((seria) => {
|
currentQuery.series.forEach((seria) => {
|
||||||
fillDataFromSeria(seria, resultColumns, currentQuery.queryName);
|
const currentOperator = getQueryOperator(
|
||||||
|
query.builder.queryData,
|
||||||
|
currentQuery.queryName,
|
||||||
|
);
|
||||||
|
|
||||||
|
fillDataFromSeria(
|
||||||
|
seria,
|
||||||
|
resultColumns,
|
||||||
|
currentQuery.queryName,
|
||||||
|
currentOperator,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,6 +326,7 @@ export const createTableColumnsFromQuery: CreateTableDataFromQuery = ({
|
|||||||
const { filledDynamicColumns, rowsLength } = fillColumnsData(
|
const { filledDynamicColumns, rowsLength } = fillColumnsData(
|
||||||
queryTableData,
|
queryTableData,
|
||||||
dynamicColumns,
|
dynamicColumns,
|
||||||
|
query,
|
||||||
);
|
);
|
||||||
|
|
||||||
const dataSource = generateData(filledDynamicColumns, rowsLength);
|
const dataSource = generateData(filledDynamicColumns, rowsLength);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Button, Col, Row } from 'antd';
|
import { Button, Col, Row } from 'antd';
|
||||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import LogsExplorerChart from 'container/LogsExplorerChart';
|
|
||||||
import LogsExplorerViews from 'container/LogsExplorerViews';
|
import LogsExplorerViews from 'container/LogsExplorerViews';
|
||||||
import { QueryBuilder } from 'container/QueryBuilder';
|
import { QueryBuilder } from 'container/QueryBuilder';
|
||||||
|
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
||||||
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||||
@ -11,23 +11,33 @@ import { DataSource } from 'types/common/queryBuilder';
|
|||||||
|
|
||||||
// ** Styles
|
// ** Styles
|
||||||
import { ButtonWrapperStyled, WrapperStyled } from './styles';
|
import { ButtonWrapperStyled, WrapperStyled } from './styles';
|
||||||
|
import { prepareQueryWithDefaultTimestamp } from './utils';
|
||||||
|
|
||||||
function LogsExplorer(): JSX.Element {
|
function LogsExplorer(): JSX.Element {
|
||||||
const { handleRunQuery, updateAllQueriesOperators } = useQueryBuilder();
|
const { handleRunQuery, updateAllQueriesOperators } = useQueryBuilder();
|
||||||
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
|
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
|
||||||
|
|
||||||
const defaultValue = useMemo(
|
const defaultValue = useMemo(() => {
|
||||||
() =>
|
const updatedQuery = updateAllQueriesOperators(
|
||||||
updateAllQueriesOperators(
|
initialQueriesMap.logs,
|
||||||
initialQueriesMap.logs,
|
PANEL_TYPES.LIST,
|
||||||
PANEL_TYPES.LIST,
|
DataSource.LOGS,
|
||||||
DataSource.LOGS,
|
);
|
||||||
),
|
return prepareQueryWithDefaultTimestamp(updatedQuery);
|
||||||
[updateAllQueriesOperators],
|
}, [updateAllQueriesOperators]);
|
||||||
);
|
|
||||||
|
|
||||||
useShareBuilderUrl(defaultValue);
|
useShareBuilderUrl(defaultValue);
|
||||||
|
|
||||||
|
const inactiveLogsFilters: QueryBuilderProps['inactiveFilters'] = useMemo(() => {
|
||||||
|
if (panelTypes === PANEL_TYPES.TABLE) {
|
||||||
|
const result: QueryBuilderProps['inactiveFilters'] = { stepInterval: true };
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}, [panelTypes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WrapperStyled>
|
<WrapperStyled>
|
||||||
<Row gutter={[0, 28]}>
|
<Row gutter={[0, 28]}>
|
||||||
@ -35,6 +45,7 @@ function LogsExplorer(): JSX.Element {
|
|||||||
<QueryBuilder
|
<QueryBuilder
|
||||||
panelType={panelTypes}
|
panelType={panelTypes}
|
||||||
config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }}
|
config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }}
|
||||||
|
inactiveFilters={inactiveLogsFilters}
|
||||||
actions={
|
actions={
|
||||||
<ButtonWrapperStyled>
|
<ButtonWrapperStyled>
|
||||||
<Button type="primary" onClick={handleRunQuery}>
|
<Button type="primary" onClick={handleRunQuery}>
|
||||||
@ -45,7 +56,6 @@ function LogsExplorer(): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24}>
|
<Col xs={24}>
|
||||||
<LogsExplorerChart />
|
|
||||||
<LogsExplorerViews />
|
<LogsExplorerViews />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
12
frontend/src/pages/LogsExplorer/utils.ts
Normal file
12
frontend/src/pages/LogsExplorer/utils.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
export const prepareQueryWithDefaultTimestamp = (query: Query): Query => ({
|
||||||
|
...query,
|
||||||
|
builder: {
|
||||||
|
...query.builder,
|
||||||
|
queryData: query.builder.queryData.map((item) => ({
|
||||||
|
...item,
|
||||||
|
orderBy: [{ columnName: 'timestamp', order: 'desc' }],
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
});
|
@ -2,10 +2,7 @@ import { Tabs } from 'antd';
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import {
|
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
||||||
COMPOSITE_QUERY,
|
|
||||||
PANEL_TYPES_QUERY,
|
|
||||||
} from 'constants/queryBuilderQueryNames';
|
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import ExportPanel from 'container/ExportPanel';
|
import ExportPanel from 'container/ExportPanel';
|
||||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||||
@ -102,11 +99,9 @@ function TracesExplorer(): JSX.Element {
|
|||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
const dashboardEditView = `${generatePath(ROUTES.DASHBOARD, {
|
const dashboardEditView = `${generatePath(ROUTES.DASHBOARD, {
|
||||||
dashboardId: data?.payload?.uuid,
|
dashboardId: data?.payload?.uuid,
|
||||||
})}/new?${QueryParams.graphType}=graph&${
|
})}/new?${QueryParams.graphType}=graph&${QueryParams.widgetId}=empty&${
|
||||||
QueryParams.widgetId
|
queryParamNamesMap.compositeQuery
|
||||||
}=empty&${COMPOSITE_QUERY}=${encodeURIComponent(
|
}=${encodeURIComponent(JSON.stringify(exportDefaultQuery))}`;
|
||||||
JSON.stringify(exportDefaultQuery),
|
|
||||||
)}`;
|
|
||||||
|
|
||||||
history.push(dashboardEditView);
|
history.push(dashboardEditView);
|
||||||
},
|
},
|
||||||
@ -132,7 +127,9 @@ function TracesExplorer(): JSX.Element {
|
|||||||
DataSource.TRACES,
|
DataSource.TRACES,
|
||||||
);
|
);
|
||||||
|
|
||||||
redirectWithQueryBuilderData(query, { [PANEL_TYPES_QUERY]: newPanelType });
|
redirectWithQueryBuilderData(query, {
|
||||||
|
[queryParamNamesMap.panelTypes]: newPanelType,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
currentQuery,
|
currentQuery,
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
MAX_QUERIES,
|
MAX_QUERIES,
|
||||||
PANEL_TYPES,
|
PANEL_TYPES,
|
||||||
} from 'constants/queryBuilder';
|
} from 'constants/queryBuilder';
|
||||||
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
|
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
||||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||||
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
|
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
|
||||||
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
||||||
@ -461,7 +461,7 @@ export function QueryBuilderProvider({
|
|||||||
};
|
};
|
||||||
|
|
||||||
urlQuery.set(
|
urlQuery.set(
|
||||||
COMPOSITE_QUERY,
|
queryParamNamesMap.compositeQuery,
|
||||||
encodeURIComponent(JSON.stringify(currentGeneratedQuery)),
|
encodeURIComponent(JSON.stringify(currentGeneratedQuery)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { notification } from 'antd';
|
import { notification } from 'antd';
|
||||||
import updateDashboardApi from 'api/dashboard/update';
|
import updateDashboardApi from 'api/dashboard/update';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
|
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
|
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
|
||||||
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
||||||
@ -88,7 +88,7 @@ export const SaveDashboard = ({
|
|||||||
};
|
};
|
||||||
const allLayout = getAllLayout();
|
const allLayout = getAllLayout();
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const compositeQuery = params.get(COMPOSITE_QUERY);
|
const compositeQuery = params.get(queryParamNamesMap.compositeQuery);
|
||||||
const { maxTime, minTime } = store.getState().globalTime;
|
const { maxTime, minTime } = store.getState().globalTime;
|
||||||
const query = compositeQuery
|
const query = compositeQuery
|
||||||
? updateStepInterval(
|
? updateStepInterval(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
export interface ILog {
|
export interface ILog {
|
||||||
date: string;
|
date: string;
|
||||||
timestamp: number;
|
timestamp: number | string;
|
||||||
id: string;
|
id: string;
|
||||||
traceId: string;
|
traceId: string;
|
||||||
spanId: string;
|
spanId: string;
|
||||||
|
@ -59,6 +59,8 @@ export type IBuilderQuery = {
|
|||||||
orderBy: OrderByPayload[];
|
orderBy: OrderByPayload[];
|
||||||
reduceTo: ReduceOperators;
|
reduceTo: ReduceOperators;
|
||||||
legend: string;
|
legend: string;
|
||||||
|
pageSize?: number;
|
||||||
|
offset?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IClickHouseQuery {
|
export interface IClickHouseQuery {
|
||||||
|
@ -5,7 +5,10 @@ export interface PayloadProps {
|
|||||||
result: QueryData[];
|
result: QueryData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ListItem = { timestamp: string; data: Omit<ILog, 'timestamp'> };
|
export type ListItem = {
|
||||||
|
timestamp: string;
|
||||||
|
data: Omit<ILog, 'timestamp'>;
|
||||||
|
};
|
||||||
|
|
||||||
export interface QueryData {
|
export interface QueryData {
|
||||||
metric: {
|
metric: {
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { QueryProps } from 'container/QueryBuilder/components/Query/Query.interfaces';
|
import { QueryProps } from 'container/QueryBuilder/components/Query/Query.interfaces';
|
||||||
|
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
||||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import { SelectOption } from './select';
|
import { SelectOption } from './select';
|
||||||
|
|
||||||
type UseQueryOperationsParams = Pick<QueryProps, 'index' | 'query'>;
|
type UseQueryOperationsParams = Pick<QueryProps, 'index' | 'query'> &
|
||||||
|
Pick<QueryBuilderProps, 'inactiveFilters'>;
|
||||||
|
|
||||||
export type HandleChangeQueryData = <
|
export type HandleChangeQueryData = <
|
||||||
Key extends keyof IBuilderQuery,
|
Key extends keyof IBuilderQuery,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user