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:
Yevhen Shevchenko 2023-07-06 14:22:44 +03:00 committed by GitHub
parent 8363dadd8d
commit 76ba364317
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 1291 additions and 501 deletions

View File

@ -0,0 +1,3 @@
import { ILog } from 'types/api/logs/log';
export type LogDetailProps = { log: ILog | null; onClose: () => void };

View 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;

View File

@ -8,13 +8,10 @@ import { useNotifications } from 'hooks/useNotifications';
// utils
import { FlatLogData } from 'lib/logs/flatLogData';
import { useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useCopyToClipboard } from 'react-use';
// interfaces
import { AppState } from 'store/reducers';
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
import { IField } from 'types/api/logs/fields';
import { ILog } from 'types/api/logs/log';
import { ILogsReducer } from 'types/reducer/logs';
// components
import AddToQueryHOC from '../AddToQueryHOC';
@ -79,24 +76,22 @@ function LogSelectedField({
interface ListLogViewProps {
logData: ILog;
onOpenDetailedView: (log: ILog) => void;
selectedFields: IField[];
}
function ListLogView({ logData }: ListLogViewProps): JSX.Element {
const {
fields: { selected },
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
const dispatch = useDispatch();
function ListLogView({
logData,
selectedFields,
onOpenDetailedView,
}: ListLogViewProps): JSX.Element {
const flattenLogData = useMemo(() => FlatLogData(logData), [logData]);
const [, setCopy] = useCopyToClipboard();
const { notifications } = useNotifications();
const handleDetailedView = useCallback(() => {
dispatch({
type: SET_DETAILED_LOG_DATA,
payload: logData,
});
}, [dispatch, logData]);
onOpenDetailedView(logData);
}, [logData, onOpenDetailedView]);
const handleCopyJSON = (): void => {
setCopy(JSON.stringify(logData, null, 2));
@ -106,8 +101,16 @@ function ListLogView({ logData }: ListLogViewProps): JSX.Element {
};
const updatedSelecedFields = useMemo(
() => selected.filter((e) => e.name !== 'id'),
[selected],
() => selectedFields.filter((e) => e.name !== 'id'),
[selectedFields],
);
const timestampValue = useMemo(
() =>
typeof flattenLogData.timestamp === 'string'
? dayjs(flattenLogData.timestamp).format()
: dayjs(flattenLogData.timestamp / 1e6).format(),
[flattenLogData.timestamp],
);
return (
@ -119,10 +122,7 @@ function ListLogView({ logData }: ListLogViewProps): JSX.Element {
{flattenLogData.stream && (
<LogGeneralField fieldKey="stream" fieldValue={flattenLogData.stream} />
)}
<LogGeneralField
fieldKey="timestamp"
fieldValue={dayjs((flattenLogData.timestamp as never) / 1e6).format()}
/>
<LogGeneralField fieldKey="timestamp" fieldValue={timestampValue} />
</>
</LogContainer>
<div>

View File

@ -30,7 +30,10 @@ function RawLogView(props: RawLogViewProps): JSX.Element {
const isDarkMode = useIsDarkMode();
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],
);

View File

@ -1,121 +1,18 @@
import { ExpandAltOutlined } from '@ant-design/icons';
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';
import { Table } from 'antd';
// styles
import { ExpandIconWrapper } from '../RawLogView/styles';
// config
import { defaultCellStyle, defaultTableStyle, tableScroll } from './config';
import { TableBodyContent } from './styles';
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();
import { tableScroll } from './config';
import { LogsTableViewProps } from './types';
import { useTableView } from './useTableView';
function LogsTableView(props: LogsTableViewProps): JSX.Element {
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 = 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]);
const { dataSource, columns } = useTableView(props);
return (
<Table
size="small"
columns={columns}
dataSource={flattenLogData}
dataSource={dataSource}
pagination={false}
rowKey="id"
bordered

View 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;
};

View 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 };
};

View File

@ -14,6 +14,7 @@ import {
IPromQLQuery,
Query,
QueryState,
TagFilter,
} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import {
@ -113,6 +114,11 @@ export const initialAutocompleteData: BaseAutocompleteData = {
type: null,
};
export const initialFilters: TagFilter = {
items: [],
op: 'AND',
};
const initialQueryBuilderFormValues: IBuilderQuery = {
dataSource: DataSource.METRICS,
queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),

View File

@ -1,2 +1,16 @@
export const COMPOSITE_QUERY = 'compositeQuery';
export const PANEL_TYPES_QUERY = 'panelTypes';
type QueryParamNames =
| 'compositeQuery'
| 'panelTypes'
| 'pageSize'
| 'viewMode'
| 'selectedFields'
| 'linesPerRow';
export const queryParamNamesMap: Record<QueryParamNames, QueryParamNames> = {
compositeQuery: 'compositeQuery',
panelTypes: 'panelTypes',
pageSize: 'pageSize',
viewMode: 'viewMode',
selectedFields: 'selectedFields',
linesPerRow: 'linesPerRow',
};

View File

@ -2,6 +2,8 @@ import { CSSProperties } from 'react';
export const ITEMS_PER_PAGE_OPTIONS = [25, 50, 100, 200];
export const DEFAULT_PER_PAGE_VALUE = 100;
export const defaultSelectStyle: CSSProperties = {
minWidth: '6rem',
};

View File

@ -0,0 +1,7 @@
import { OptionsMenuConfig } from 'container/OptionsMenu/types';
export type ExplorerControlPanelProps = {
isShowPageSize: boolean;
isLoading: boolean;
optionsMenuConfig?: OptionsMenuConfig;
};

View 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;

View File

@ -0,0 +1,5 @@
import styled from 'styled-components';
export const ContainerStyled = styled.div`
margin-bottom: 0.3rem;
`;

View File

@ -1,5 +1,5 @@
import { Button, Dropdown, MenuProps, Modal } from 'antd';
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import { useCallback, useMemo, useState } from 'react';
@ -22,9 +22,9 @@ function ExportPanel({
const onCreateAlertsHandler = useCallback(() => {
history.push(
`${ROUTES.ALERTS_NEW}?${COMPOSITE_QUERY}=${encodeURIComponent(
JSON.stringify(query),
)}`,
`${ROUTES.ALERTS_NEW}?${
queryParamNamesMap.compositeQuery
}=${encodeURIComponent(JSON.stringify(query))}`,
);
}, [query]);

View File

@ -9,7 +9,7 @@ import {
import { Dropdown, MenuProps, Tooltip, Typography } from 'antd';
import { MenuItemType } from 'antd/es/menu/hooks/useItems';
import Spinner from 'components/Spinner';
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import useComponentPermission from 'hooks/useComponentPermission';
import history from 'lib/history';
import { useCallback, useMemo, useState } from 'react';
@ -64,7 +64,9 @@ function WidgetHeader({
history.push(
`${window.location.pathname}/new?widgetId=${widgetId}&graphType=${
widget.panelTypes
}&${COMPOSITE_QUERY}=${encodeURIComponent(JSON.stringify(widget.query))}`,
}&${queryParamNamesMap.compositeQuery}=${encodeURIComponent(
JSON.stringify(widget.query),
)}`,
);
}, [widget.id, widget.panelTypes, widget.query]);

View File

@ -5,7 +5,7 @@ import { ColumnsType } from 'antd/lib/table';
import saveAlertApi from 'api/alerts/save';
import { ResizeTable } from 'components/ResizeTable';
import TextToolTip from 'components/TextToolTip';
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import useInterval from 'hooks/useInterval';
@ -75,11 +75,9 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
const compositeQuery = mapQueryDataFromApi(record.condition.compositeQuery);
history.push(
`${
ROUTES.EDIT_ALERTS
}?ruleId=${record.id.toString()}&${COMPOSITE_QUERY}=${encodeURIComponent(
JSON.stringify(compositeQuery),
)}`,
`${ROUTES.EDIT_ALERTS}?ruleId=${record.id.toString()}&${
queryParamNamesMap.compositeQuery
}=${encodeURIComponent(JSON.stringify(compositeQuery))}`,
);
})
.catch(handleError);

View File

@ -83,12 +83,17 @@ function LogControls(): JSX.Element | null {
const flattenLogData = useMemo(
() =>
logs.map((log) =>
FlatLogData({
logs.map((log) => {
const timestamp =
typeof log.timestamp === 'string'
? dayjs(log.timestamp).format()
: dayjs(log.timestamp / 1e6).format();
return FlatLogData({
...log,
timestamp: (dayjs(log.timestamp / 1e6).format() as unknown) as number,
timestamp,
});
}),
),
[logs],
);

View File

@ -1,4 +1,4 @@
import { Drawer, Tabs } from 'antd';
import LogDetail from 'components/LogDetail';
import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import { AppState } from 'store/reducers';
@ -6,9 +6,6 @@ import AppActions from 'types/actions';
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
import { ILogsReducer } from 'types/reducer/logs';
import JSONView from './JsonView';
import TableView from './TableView';
function LogDetailedView(): JSX.Element {
const { detailedLog } = useSelector<AppState, ILogsReducer>(
(state) => state.logs,
@ -23,33 +20,7 @@ function LogDetailedView(): JSX.Element {
});
};
const items = [
{
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>
);
return <LogDetail log={detailedLog} onClose={onDrawerClose} />;
}
export default LogDetailedView;

View File

@ -0,0 +1,6 @@
import { ILog } from 'types/api/logs/log';
export type LogExplorerDetailedViewProps = {
log: ILog | null;
onClose: () => void;
};

View 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;

View File

@ -0,0 +1,6 @@
import { QueryData } from 'types/api/widgets/getQuery';
export type LogsExplorerChartProps = {
data: QueryData[];
isLoading: boolean;
};

View File

@ -1,48 +1,43 @@
import Graph from 'components/Graph';
import Spinner from 'components/Spinner';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { getExplorerChartData } from 'lib/explorer/getExplorerChartData';
import getChartData, { GetChartDataProps } from 'lib/getChartData';
import { colors } from 'lib/getRandomColor';
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';
function LogsExplorerChart(): JSX.Element {
const { stagedQuery, panelType, isEnabledQuery } = useQueryBuilder();
function LogsExplorerChart({
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>(
(state) => state.globalTime,
);
const { data, isFetching } = useGetQueryRange(
const graphData = useMemo(
() =>
getChartData({
queryData: [
{
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,
queryData: data,
},
],
createDataset: handleCreateDatasets,
}),
[data],
);
const graphData = useMemo(() => {
if (data?.payload.data && data.payload.data.result.length > 0) {
return getExplorerChartData([data.payload.data.result[0]]);
}
return getExplorerChartData([]);
}, [data]);
return (
<CardStyled>
{isFetching ? (
{isLoading ? (
<Spinner size="default" height="100%" />
) : (
<Graph

View File

@ -0,0 +1,6 @@
import { CSSProperties } from 'react';
export const infinityDefaultStyles: CSSProperties = {
height: 'auto',
width: '100%',
};

View File

@ -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;

View File

@ -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;
}
`;

View File

@ -0,0 +1,8 @@
import { LogsTableViewProps } from 'components/Logs/TableView/types';
export type InfinityTableProps = {
tableViewProps: LogsTableViewProps;
infitiyTableProps: {
onEndReached: (index: number) => void;
};
};

View File

@ -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;
};

View File

@ -2,38 +2,43 @@ import { Card, Typography } from 'antd';
// components
import ListLogView from 'components/Logs/ListLogView';
import RawLogView from 'components/Logs/RawLogView';
import LogsTableView from 'components/Logs/TableView';
import Spinner from 'components/Spinner';
import { LogViewMode } from 'container/LogsTable';
import { Container, Heading } from 'container/LogsTable/styles';
import ExplorerControlPanel from 'container/ExplorerControlPanel';
import { Heading } from 'container/LogsTable/styles';
import { useOptionsMenu } from 'container/OptionsMenu';
import { contentStyle } from 'container/Trace/Search/config';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import useFontFaceObserver from 'hooks/useFontObserver';
import { memo, useCallback, useMemo, useState } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { Virtuoso } from 'react-virtuoso';
// interfaces
import { ILog } from 'types/api/logs/log';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
import InfinityTableView from './InfinityTableView';
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({
data,
isLoading,
currentStagedQueryData,
logs,
onOpenDetailedView,
onEndReached,
onExpand,
}: LogsExplorerListProps): JSX.Element {
const [viewMode] = useState<LogViewMode>('raw');
const [linesPerRow] = useState<number>(20);
const { initialDataSource } = useQueryBuilder();
const logs: ILog[] = useMemo(() => {
if (data.length > 0 && data[0].list) {
const logs: ILog[] = data[0].list.map((item) => ({
timestamp: +item.timestamp,
...item.data,
}));
return logs;
}
return [];
}, [data]);
const { options, config } = useOptionsMenu({
dataSource: initialDataSource || DataSource.METRICS,
aggregateOperator:
currentStagedQueryData?.aggregateOperator || StringOperators.NOOP,
});
useFontFaceObserver(
[
@ -42,75 +47,107 @@ function LogsExplorerList({
weight: '300',
},
],
viewMode === 'raw',
options.format === 'raw',
{
timeout: 5000,
},
);
// TODO: implement here linesPerRow, mode like in useSelectedLogView
const selectedFields = useMemo(
() => convertKeysToColumnFields(options.selectColumns),
[options],
);
const getItemContent = useCallback(
(index: number): JSX.Element => {
const log = logs[index];
if (viewMode === 'raw') {
(_: number, log: ILog): JSX.Element => {
if (options.format === 'raw') {
return (
<RawLogView
key={log.id}
data={log}
linesPerRow={linesPerRow}
// TODO: write new onClickExpanded logic
onClickExpand={(): void => {}}
linesPerRow={options.maxLines}
onClickExpand={onExpand}
/>
);
}
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(() => {
if (viewMode === 'table') {
const components = isLoading
? {
Footer,
}
: {};
if (options.format === 'table') {
return (
<LogsTableView
logs={logs}
// TODO: write new selected logic
fields={[]}
linesPerRow={linesPerRow}
// TODO: write new onClickExpanded logic
onClickExpand={(): void => {}}
<InfinityTableView
tableViewProps={{
logs,
fields: selectedFields,
linesPerRow: options.maxLines,
onClickExpand: onExpand,
}}
infitiyTableProps={{ onEndReached }}
/>
);
}
return (
<Card bodyStyle={contentStyle}>
<Card style={{ width: '100%' }} bodyStyle={{ ...contentStyle }}>
<Virtuoso
useWindowScroll
data={logs}
endReached={onEndReached}
totalCount={logs.length}
itemContent={getItemContent}
components={components}
/>
</Card>
);
}, [getItemContent, linesPerRow, logs, viewMode]);
if (isLoading) {
return <Spinner height={20} tip="Getting Logs" />;
}
}, [
isLoading,
logs,
options.format,
options.maxLines,
onEndReached,
getItemContent,
selectedFields,
onExpand,
]);
return (
<Container>
{viewMode !== 'table' && (
<>
<ExplorerControlPanel
isLoading={isLoading}
isShowPageSize={false}
optionsMenuConfig={config}
/>
{options.format !== 'table' && (
<Heading>
<Typography.Text>Event</Typography.Text>
</Heading>
)}
{logs.length === 0 && <Typography>No logs lines found</Typography>}
{renderContent}
</Container>
<InfinityWrapperStyled>{renderContent}</InfinityWrapperStyled>
</>
);
}

View File

@ -0,0 +1,6 @@
import styled from 'styled-components';
export const InfinityWrapperStyled = styled.div`
min-height: 40rem;
display: flex;
`;

View 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,
}));

View File

@ -6,8 +6,8 @@ import { memo } from 'react';
import { LogsExplorerTableProps } from './LogsExplorerTable.interfaces';
function LogsExplorerTable({
isLoading,
data,
isLoading,
}: LogsExplorerTableProps): JSX.Element {
const { stagedQuery } = useQueryBuilder();

View File

@ -1,50 +1,62 @@
import { TabsProps } from 'antd';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { PANEL_TYPES_QUERY } from 'constants/queryBuilderQueryNames';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
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 LogsExplorerTable from 'container/LogsExplorerTable';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
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 { memo, useCallback, useEffect, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import useUrlQueryData from 'hooks/useUrlQueryData';
import { getPaginationQueryData } from 'lib/newQueryBuilder/getPaginationQueryData';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { ILog } from 'types/api/logs/log';
import {
IBuilderQuery,
OrderByPayload,
Query,
} from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
import { TabsStyled } from './LogsExplorerViews.styled';
function LogsExplorerViews(): JSX.Element {
const { queryData: pageSize } = useUrlQueryData(
queryParamNamesMap.pageSize,
DEFAULT_PER_PAGE_VALUE,
);
// Context
const {
currentQuery,
stagedQuery,
panelType,
isEnabledQuery,
updateAllQueriesOperators,
redirectWithQueryBuilderData,
} = useQueryBuilder();
const { selectedTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
// State
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 currentStagedQueryData = useMemo(() => {
if (!stagedQuery || stagedQuery.builder.queryData.length !== 1) return null;
return stagedQuery.builder.queryData[0];
}, [stagedQuery]);
const orderByTimestamp: OrderByPayload | null = useMemo(() => {
const timestampOrderBy = currentStagedQueryData?.orderBy.find(
(item) => item.columnName === 'timestamp',
);
const { data, isFetching, isError } = useGetQueryRange(
{
query: stagedQuery || initialQueriesMap.metrics,
graphType: panelType || PANEL_TYPES.LIST,
globalSelectedInterval: selectedTime,
selectedTime: 'GLOBAL_TIME',
params: {
dataSource: DataSource.LOGS,
},
},
{
queryKey: [REACT_QUERY_KEY.GET_QUERY_RANGE, selectedTime, stagedQuery],
enabled: isEnabledQuery,
},
);
return timestampOrderBy || null;
}, [currentStagedQueryData]);
const isMultipleQueries = useMemo(
() =>
@ -62,35 +74,57 @@ function LogsExplorerViews(): JSX.Element {
return groupByCount > 0;
}, [currentQuery]);
const currentData = useMemo(
() => data?.payload.data.newResult.data.result || [],
[data],
const isLimit: boolean = useMemo(() => {
if (!currentStagedQueryData) return false;
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,
disabled: isMultipleQueries || isGroupByExist,
children: <LogsExplorerList data={currentData} isLoading={isFetching} />,
keepPreviousData: true,
enabled: !isLimit,
},
{
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(
(newPanelType: string) => {
if (newPanelType === panelType) return;
@ -101,7 +135,9 @@ function LogsExplorerViews(): JSX.Element {
DataSource.LOGS,
);
redirectWithQueryBuilderData(query, { [PANEL_TYPES_QUERY]: newPanelType });
redirectWithQueryBuilderData(query, {
[queryParamNamesMap.panelTypes]: newPanelType,
});
},
[
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(() => {
const shouldChangeView = isMultipleQueries || isGroupByExist;
@ -119,15 +224,115 @@ function LogsExplorerViews(): JSX.Element {
}
}, [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 (
<div>
<>
<LogsExplorerChart isLoading={isFetching} data={chartData} />
<TabsStyled
items={tabsItems}
defaultActiveKey={panelType || PANEL_TYPES.LIST}
activeKey={panelType || PANEL_TYPES.LIST}
onChange={handleChangeView}
destroyInactiveTabPane
/>
</div>
<LogExplorerDetailedView log={activeLog} onClose={handleClearActiveLog} />
</>
);
}

View File

@ -7,10 +7,11 @@ import Spinner from 'components/Spinner';
import { contentStyle } from 'container/Trace/Search/config';
import useFontFaceObserver from 'hooks/useFontObserver';
import { memo, useCallback, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { Virtuoso } from 'react-virtuoso';
// interfaces
import { AppState } from 'store/reducers';
// interfaces
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
import { ILog } from 'types/api/logs/log';
import { ILogsReducer } from 'types/reducer/logs';
@ -28,6 +29,8 @@ type LogsTableProps = {
function LogsTable(props: LogsTableProps): JSX.Element {
const { viewMode, onClickExpand, linesPerRow } = props;
const dispatch = useDispatch();
useFontFaceObserver(
[
{
@ -58,6 +61,16 @@ function LogsTable(props: LogsTableProps): JSX.Element {
liveTail,
]);
const handleOpenDetailedView = useCallback(
(logData: ILog) => {
dispatch({
type: SET_DETAILED_LOG_DATA,
payload: logData,
});
},
[dispatch],
);
const getItemContent = useCallback(
(index: number): JSX.Element => {
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(() => {

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { initialQueriesMap } from 'constants/queryBuilder';
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
@ -47,7 +47,7 @@ function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element {
history.push(
`${history.location.pathname}/new?graphType=${name}&widgetId=${
emptyLayout.i
}&${COMPOSITE_QUERY}=${encodeURIComponent(
}&${queryParamNamesMap.compositeQuery}=${encodeURIComponent(
JSON.stringify(initialQueriesMap.metrics),
)}`,
);

View File

@ -18,9 +18,9 @@ function FormatField({ config }: FormatFieldProps): JSX.Element | null {
value={config.value}
onChange={config.onChange}
>
<RadioButton value="row">{t('options_menu.row')}</RadioButton>
<RadioButton value="default">{t('options_menu.default')}</RadioButton>
<RadioButton value="column">{t('options_menu.column')}</RadioButton>
<RadioButton value="raw">{t('options_menu.row')}</RadioButton>
<RadioButton value="list">{t('options_menu.default')}</RadioButton>
<RadioButton value="table">{t('options_menu.column')}</RadioButton>
</RadioGroup>
</FormatFieldWrapper>
);

View File

@ -4,6 +4,6 @@ export const URL_OPTIONS = 'options';
export const defaultOptionsQuery: OptionsQuery = {
selectColumns: [],
maxLines: 0,
format: 'default',
maxLines: 2,
format: 'list',
};

View File

@ -1,10 +1,11 @@
import { InputNumberProps, RadioProps, SelectProps } from 'antd';
import { LogViewMode } from 'container/LogsTable';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
export interface OptionsQuery {
selectColumns: BaseAutocompleteData[];
maxLines: number;
format: 'default' | 'row' | 'column';
format: LogViewMode;
}
export interface InitialOptions

View File

@ -35,16 +35,17 @@ const useOptionsMenu = ({
query: optionsQuery,
queryData: optionsQueryData,
redirectWithQuery: redirectWithOptionsData,
} = useUrlQueryData<OptionsQuery>(URL_OPTIONS);
} = useUrlQueryData<OptionsQuery>(URL_OPTIONS, defaultOptionsQuery);
const { data, isFetched, isLoading } = useQuery(
[QueryBuilderKeys.GET_ATTRIBUTE_KEY],
[QueryBuilderKeys.GET_ATTRIBUTE_KEY, dataSource, aggregateOperator],
async () =>
getAggregateKeys({
searchText: '',
dataSource,
aggregateOperator,
aggregateAttribute: '',
tagType: null,
}),
);
@ -86,11 +87,16 @@ const useOptionsMenu = ({
}, [] as BaseAutocompleteData[]);
redirectWithOptionsData({
...defaultOptionsQuery,
...optionsQueryData,
selectColumns: newSelectedColumns,
});
},
[attributeKeys, selectedColumnKeys, redirectWithOptionsData],
[
selectedColumnKeys,
redirectWithOptionsData,
optionsQueryData,
attributeKeys,
],
);
const handleRemoveSelectedColumn = useCallback(
@ -116,21 +122,21 @@ const useOptionsMenu = ({
const handleFormatChange = useCallback(
(event: RadioChangeEvent) => {
redirectWithOptionsData({
...defaultOptionsQuery,
...optionsQueryData,
format: event.target.value,
});
},
[redirectWithOptionsData],
[optionsQueryData, redirectWithOptionsData],
);
const handleMaxLinesChange = useCallback(
(value: string | number | null) => {
redirectWithOptionsData({
...defaultOptionsQuery,
...optionsQueryData,
maxLines: value as number,
});
},
[redirectWithOptionsData],
[optionsQueryData, redirectWithOptionsData],
);
const optionsMenuConfig: Required<OptionsMenuConfig> = useMemo(

View File

@ -0,0 +1,4 @@
export type PageSizeSelectProps = {
isLoading: boolean;
isShow: boolean;
};

View 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;

View File

@ -1,5 +1,6 @@
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
import { ReactNode } from 'react';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
export type QueryBuilderConfig =
@ -13,4 +14,5 @@ export type QueryBuilderProps = {
config?: QueryBuilderConfig;
panelType: ITEMS;
actions?: ReactNode;
inactiveFilters?: Partial<Record<keyof IBuilderQuery, boolean>>;
};

View File

@ -17,6 +17,7 @@ export const QueryBuilder = memo(function QueryBuilder({
config,
panelType: newPanelType,
actions,
inactiveFilters = {},
}: QueryBuilderProps): JSX.Element {
const {
currentQuery,
@ -74,6 +75,7 @@ export const QueryBuilder = memo(function QueryBuilder({
isAvailableToDisable={isAvailableToDisableQuery}
queryVariant={config?.queryVariant || 'dropdown'}
query={query}
inactiveFilters={inactiveFilters}
/>
</Col>
))}

View File

@ -1,3 +1,4 @@
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
export type QueryProps = {
@ -5,4 +6,4 @@ export type QueryProps = {
isAvailableToDisable: boolean;
query: IBuilderQuery;
queryVariant: 'static' | 'dropdown';
};
} & Pick<QueryBuilderProps, 'inactiveFilters'>;

View File

@ -35,6 +35,7 @@ export const Query = memo(function Query({
isAvailableToDisable,
queryVariant,
query,
inactiveFilters,
}: QueryProps): JSX.Element {
const { panelType } = useQueryBuilder();
const {
@ -47,7 +48,7 @@ export const Query = memo(function Query({
handleChangeQueryData,
handleChangeOperator,
handleDeleteQuery,
} = useQueryOperations({ index, query });
} = useQueryOperations({ index, query, inactiveFilters });
const handleChangeAggregateEvery = useCallback(
(value: IBuilderQuery['stepInterval']) => {
@ -109,6 +110,24 @@ export const Query = memo(function Query({
[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 => {
switch (panelType) {
case PANEL_TYPES.TIME_SERIES: {
@ -149,19 +168,7 @@ export const Query = memo(function Query({
</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>
<Col span={11}>{renderAggregateEveryFilter()}</Col>
</>
);
}
@ -179,19 +186,7 @@ export const Query = memo(function Query({
</Col>
</Row>
</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>
<Col span={11}>{renderAggregateEveryFilter()}</Col>
</>
);
}
@ -230,21 +225,7 @@ export const Query = memo(function Query({
</Row>
</Col>
{panelType !== PANEL_TYPES.LIST && (
<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>
)}
<Col span={11}>{renderAggregateEveryFilter()}</Col>
</>
);
}
@ -253,10 +234,10 @@ export const Query = memo(function Query({
panelType,
query,
isMetricsDataSource,
handleChangeAggregateEvery,
handleChangeHavingFilter,
handleChangeLimit,
handleChangeOrderByKeys,
renderAggregateEveryFilter,
]);
return (

View File

@ -27,7 +27,7 @@ export function OrderByFilter({
}: OrderByFilterProps): JSX.Element {
const [searchText, setSearchText] = useState<string>('');
const [selectedValue, setSelectedValue] = useState<IOption[]>(
transformToOrderByStringValues(query.orderBy) || [],
transformToOrderByStringValues(query.orderBy),
);
const { data, isFetching } = useQuery(

View File

@ -8,11 +8,25 @@ export const orderByValueDelimiter = '|';
export const transformToOrderByStringValues = (
orderBy: OrderByPayload[],
): IOption[] =>
orderBy.map((item) => ({
): IOption[] => {
const prepareSelectedValue: IOption[] = orderBy.reduce<IOption[]>(
(acc, item) => {
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(
arr: BaseAutocompleteData[],

View File

@ -1,4 +1,4 @@
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import useUrlQuery from 'hooks/useUrlQuery';
import { useMemo } from 'react';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
@ -7,7 +7,7 @@ export const useGetCompositeQueryParam = (): Query | null => {
const urlQuery = useUrlQuery();
return useMemo(() => {
const compositeQuery = urlQuery.get(COMPOSITE_QUERY);
const compositeQuery = urlQuery.get(queryParamNamesMap.compositeQuery);
let parsedCompositeQuery: Query | null = null;
try {

View 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,
},
);
};

View File

@ -1,4 +1,4 @@
import { PANEL_TYPES_QUERY } from 'constants/queryBuilderQueryNames';
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import useUrlQuery from 'hooks/useUrlQuery';
import { useMemo } from 'react';
@ -9,7 +9,7 @@ export const useGetPanelTypesQueryParam = <T extends GRAPH_TYPES | undefined>(
const urlQuery = useUrlQuery();
return useMemo(() => {
const panelTypeQuery = urlQuery.get(PANEL_TYPES_QUERY);
const panelTypeQuery = urlQuery.get(queryParamNamesMap.panelTypes);
return panelTypeQuery ? JSON.parse(panelTypeQuery) : defaultPanelType;
}, [urlQuery, defaultPanelType]);

View File

@ -17,7 +17,11 @@ import {
import { DataSource } from 'types/common/queryBuilder';
import { SelectOption } from 'types/common/select';
export const useQueryOperations: UseQueryOperations = ({ query, index }) => {
export const useQueryOperations: UseQueryOperations = ({
query,
index,
inactiveFilters,
}) => {
const {
handleSetQueryData,
removeQueryBuilderEntityByIndex,
@ -58,15 +62,23 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => {
const getNewListOfAdditionalFilters = useCallback(
(dataSource: DataSource): string[] => {
const listOfFilters = mapOfFilters[dataSource].map((item) => item.text);
if (panelType === PANEL_TYPES.LIST) {
return listOfFilters.filter((filter) => filter !== 'Aggregation interval');
const result: string[] = mapOfFilters[dataSource].reduce<string[]>(
(acc, item) => {
if (inactiveFilters && inactiveFilters[item.field]) {
return acc;
}
return listOfFilters;
acc.push(item.text);
return acc;
},
[panelType],
[],
);
return result;
},
[inactiveFilters],
);
const handleChangeAggregatorAttribute = useCallback(

View File

@ -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;
};

View File

@ -1,11 +1,14 @@
import { ChartData } from 'chart.js';
import { ChartData, ChartDataset } from 'chart.js';
import getLabelName from 'lib/getLabelName';
import { QueryData } from 'types/api/widgets/getQuery';
import convertIntoEpoc from './covertIntoEpoc';
import { colors } from './getRandomColor';
const getChartData = ({ queryData }: GetChartDataProps): ChartData => {
const getChartData = ({
queryData,
createDataset,
}: GetChartDataProps): ChartData => {
const uniqueTimeLabels = new Set<number>();
queryData.forEach((data) => {
data.queryData.forEach((query) => {
@ -60,28 +63,39 @@ const getChartData = ({ queryData }: GetChartDataProps): ChartData => {
.reduce((a, b) => [...a, ...b], []);
return {
datasets: alldata.map((e, index) => ({
data: e,
datasets: alldata.map((e, index) => {
const datasetBaseConfig = {
label: allLabels[index],
borderColor: colors[index % colors.length] || 'red',
data: e,
borderWidth: 1.5,
spanGaps: true,
animations: false,
borderColor: colors[index % colors.length] || 'red',
showLine: true,
pointRadius: 0,
})),
};
return createDataset
? createDataset(e, index, allLabels)
: datasetBaseConfig;
}),
labels: response
.map((e) => e.map((e) => e.first))
.reduce((a, b) => [...a, ...b], [])[0],
};
};
interface GetChartDataProps {
export interface GetChartDataProps {
queryData: {
query?: string;
legend?: string;
queryData: QueryData[];
}[];
createDataset?: (
element: (number | null)[],
index: number,
allLabels: string[],
) => ChartDataset;
}
export default getChartData;

View 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 };
};

View File

@ -39,6 +39,7 @@ type CreateTableDataFromQuery = (
type FillColumnData = (
queryTableData: QueryDataV3[],
dynamicColumns: DynamicColumns,
query: Query,
) => { filledDynamicColumns: DynamicColumns; rowsLength: number };
type GetDynamicColumns = (
@ -177,7 +178,8 @@ const fillEmptyRowCells = (
const fillDataFromSeria = (
seria: SeriesItem,
columns: DynamicColumns,
currentQueryName: string,
queryName: string,
operator: string,
): void => {
const labelEntries = Object.entries(seria.labels);
@ -193,7 +195,13 @@ const fillDataFromSeria = (
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));
unusedColumnsKeys.delete(column.key);
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 operators = cols.filter((item) => item.type === 'operator');
const resultColumns = [...fields, ...operators];
queryTableData.forEach((currentQuery) => {
// const currentOperator = getQueryOperator(
// query.builder.queryData,
// currentQuery.queryName,
// );
if (currentQuery.series) {
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(
queryTableData,
dynamicColumns,
query,
);
const dataSource = generateData(filledDynamicColumns, rowsLength);

View File

@ -1,8 +1,8 @@
import { Button, Col, Row } from 'antd';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import LogsExplorerChart from 'container/LogsExplorerChart';
import LogsExplorerViews from 'container/LogsExplorerViews';
import { QueryBuilder } from 'container/QueryBuilder';
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
@ -11,23 +11,33 @@ import { DataSource } from 'types/common/queryBuilder';
// ** Styles
import { ButtonWrapperStyled, WrapperStyled } from './styles';
import { prepareQueryWithDefaultTimestamp } from './utils';
function LogsExplorer(): JSX.Element {
const { handleRunQuery, updateAllQueriesOperators } = useQueryBuilder();
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
const defaultValue = useMemo(
() =>
updateAllQueriesOperators(
const defaultValue = useMemo(() => {
const updatedQuery = updateAllQueriesOperators(
initialQueriesMap.logs,
PANEL_TYPES.LIST,
DataSource.LOGS,
),
[updateAllQueriesOperators],
);
return prepareQueryWithDefaultTimestamp(updatedQuery);
}, [updateAllQueriesOperators]);
useShareBuilderUrl(defaultValue);
const inactiveLogsFilters: QueryBuilderProps['inactiveFilters'] = useMemo(() => {
if (panelTypes === PANEL_TYPES.TABLE) {
const result: QueryBuilderProps['inactiveFilters'] = { stepInterval: true };
return result;
}
return {};
}, [panelTypes]);
return (
<WrapperStyled>
<Row gutter={[0, 28]}>
@ -35,6 +45,7 @@ function LogsExplorer(): JSX.Element {
<QueryBuilder
panelType={panelTypes}
config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }}
inactiveFilters={inactiveLogsFilters}
actions={
<ButtonWrapperStyled>
<Button type="primary" onClick={handleRunQuery}>
@ -45,7 +56,6 @@ function LogsExplorer(): JSX.Element {
/>
</Col>
<Col xs={24}>
<LogsExplorerChart />
<LogsExplorerViews />
</Col>
</Row>

View 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' }],
})),
},
});

View File

@ -2,10 +2,7 @@ import { Tabs } from 'antd';
import axios from 'axios';
import { QueryParams } from 'constants/query';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import {
COMPOSITE_QUERY,
PANEL_TYPES_QUERY,
} from 'constants/queryBuilderQueryNames';
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import ROUTES from 'constants/routes';
import ExportPanel from 'container/ExportPanel';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
@ -102,11 +99,9 @@ function TracesExplorer(): JSX.Element {
onSuccess: (data) => {
const dashboardEditView = `${generatePath(ROUTES.DASHBOARD, {
dashboardId: data?.payload?.uuid,
})}/new?${QueryParams.graphType}=graph&${
QueryParams.widgetId
}=empty&${COMPOSITE_QUERY}=${encodeURIComponent(
JSON.stringify(exportDefaultQuery),
)}`;
})}/new?${QueryParams.graphType}=graph&${QueryParams.widgetId}=empty&${
queryParamNamesMap.compositeQuery
}=${encodeURIComponent(JSON.stringify(exportDefaultQuery))}`;
history.push(dashboardEditView);
},
@ -132,7 +127,9 @@ function TracesExplorer(): JSX.Element {
DataSource.TRACES,
);
redirectWithQueryBuilderData(query, { [PANEL_TYPES_QUERY]: newPanelType });
redirectWithQueryBuilderData(query, {
[queryParamNamesMap.panelTypes]: newPanelType,
});
},
[
currentQuery,

View File

@ -13,7 +13,7 @@ import {
MAX_QUERIES,
PANEL_TYPES,
} from 'constants/queryBuilder';
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
@ -461,7 +461,7 @@ export function QueryBuilderProvider({
};
urlQuery.set(
COMPOSITE_QUERY,
queryParamNamesMap.compositeQuery,
encodeURIComponent(JSON.stringify(currentGeneratedQuery)),
);

View File

@ -1,7 +1,7 @@
import { notification } from 'antd';
import updateDashboardApi from 'api/dashboard/update';
import { AxiosError } from 'axios';
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import ROUTES from 'constants/routes';
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
@ -88,7 +88,7 @@ export const SaveDashboard = ({
};
const allLayout = getAllLayout();
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 query = compositeQuery
? updateStepInterval(

View File

@ -1,6 +1,6 @@
export interface ILog {
date: string;
timestamp: number;
timestamp: number | string;
id: string;
traceId: string;
spanId: string;

View File

@ -59,6 +59,8 @@ export type IBuilderQuery = {
orderBy: OrderByPayload[];
reduceTo: ReduceOperators;
legend: string;
pageSize?: number;
offset?: number;
};
export interface IClickHouseQuery {

View File

@ -5,7 +5,10 @@ export interface PayloadProps {
result: QueryData[];
}
export type ListItem = { timestamp: string; data: Omit<ILog, 'timestamp'> };
export type ListItem = {
timestamp: string;
data: Omit<ILog, 'timestamp'>;
};
export interface QueryData {
metric: {

View File

@ -1,11 +1,13 @@
import { QueryProps } from 'container/QueryBuilder/components/Query/Query.interfaces';
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { SelectOption } from './select';
type UseQueryOperationsParams = Pick<QueryProps, 'index' | 'query'>;
type UseQueryOperationsParams = Pick<QueryProps, 'index' | 'query'> &
Pick<QueryBuilderProps, 'inactiveFilters'>;
export type HandleChangeQueryData = <
Key extends keyof IBuilderQuery,