diff --git a/frontend/src/components/LogDetail/LogDetail.interfaces.ts b/frontend/src/components/LogDetail/LogDetail.interfaces.ts new file mode 100644 index 000000000000..8e4ed857f0cc --- /dev/null +++ b/frontend/src/components/LogDetail/LogDetail.interfaces.ts @@ -0,0 +1,3 @@ +import { ILog } from 'types/api/logs/log'; + +export type LogDetailProps = { log: ILog | null; onClose: () => void }; diff --git a/frontend/src/components/LogDetail/index.tsx b/frontend/src/components/LogDetail/index.tsx new file mode 100644 index 000000000000..054a9c31610a --- /dev/null +++ b/frontend/src/components/LogDetail/index.tsx @@ -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 && , + }, + { + label: 'JSON', + key: '2', + children: log && , + }, + ]; + + return ( + + + + ); +} + +export default LogDetail; diff --git a/frontend/src/components/Logs/ListLogView/index.tsx b/frontend/src/components/Logs/ListLogView/index.tsx index ebee0899c716..a4f1e9cc9c5b 100644 --- a/frontend/src/components/Logs/ListLogView/index.tsx +++ b/frontend/src/components/Logs/ListLogView/index.tsx @@ -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((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 && ( )} - +
diff --git a/frontend/src/components/Logs/RawLogView/index.tsx b/frontend/src/components/Logs/RawLogView/index.tsx index 711b4420ad00..76d12c1a2287 100644 --- a/frontend/src/components/Logs/RawLogView/index.tsx +++ b/frontend/src/components/Logs/RawLogView/index.tsx @@ -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], ); diff --git a/frontend/src/components/Logs/TableView/index.tsx b/frontend/src/components/Logs/TableView/index.tsx index 5ec91d87d76c..014ced58a5ee 100644 --- a/frontend/src/components/Logs/TableView/index.tsx +++ b/frontend/src/components/Logs/TableView/index.tsx @@ -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 = ReturnType< - NonNullable['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> = useMemo(() => { - const fieldColumns: ColumnsType> = fields - .filter((e) => e.name !== 'id') - .map(({ name }) => ({ - title: name, - dataIndex: name, - key: name, - render: (field): ColumnTypeRender> => ({ - props: { - style: defaultCellStyle, - }, - children: ( - - {field} - - ), - }), - })); - - return [ - { - title: '', - dataIndex: 'id', - key: 'expand', - // https://github.com/ant-design/ant-design/discussions/36886 - render: (_, item): ColumnTypeRender> => ({ - props: { - style: defaultCellStyle, - }, - children: ( - { - onClickExpand((item as unknown) as ILog); - }} - > - - - ), - }), - }, - { - title: 'timestamp', - dataIndex: 'timestamp', - key: 'timestamp', - // https://github.com/ant-design/ant-design/discussions/36886 - render: (field): ColumnTypeRender> => { - const date = dayjs(field / 1e6).format(); - return { - children: {date}, - }; - }, - }, - ...fieldColumns, - { - title: 'body', - dataIndex: 'body', - key: 'body', - render: (field): ColumnTypeRender> => ({ - props: { - style: defaultTableStyle, - }, - children: ( - - ), - }), - }, - ]; - }, [fields, linesPerRow, onClickExpand]); + const { dataSource, columns } = useTableView(props); return ( = ReturnType< + NonNullable['render']> +>; + +export type LogsTableViewProps = { + logs: ILog[]; + fields: IField[]; + linesPerRow: number; + onClickExpand: (log: ILog) => void; +}; diff --git a/frontend/src/components/Logs/TableView/useTableView.tsx b/frontend/src/components/Logs/TableView/useTableView.tsx new file mode 100644 index 000000000000..fb66235ceff7 --- /dev/null +++ b/frontend/src/components/Logs/TableView/useTableView.tsx @@ -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>; + dataSource: Record[]; +}; + +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> = useMemo(() => { + const fieldColumns: ColumnsType> = fields + .filter((e) => e.name !== 'id') + .map(({ name }) => ({ + title: name, + dataIndex: name, + key: name, + render: (field): ColumnTypeRender> => ({ + props: { + style: defaultCellStyle, + }, + children: ( + + {field} + + ), + }), + })); + + return [ + { + title: '', + dataIndex: 'id', + key: 'expand', + // https://github.com/ant-design/ant-design/discussions/36886 + render: (_, item): ColumnTypeRender> => ({ + props: { + style: defaultCellStyle, + }, + children: ( + { + onClickExpand((item as unknown) as ILog); + }} + > + + + ), + }), + }, + { + title: 'timestamp', + dataIndex: 'timestamp', + key: 'timestamp', + // https://github.com/ant-design/ant-design/discussions/36886 + render: (field): ColumnTypeRender> => { + const date = + typeof field === 'string' + ? dayjs(field).format() + : dayjs(field / 1e6).format(); + return { + children: {date}, + }; + }, + }, + ...fieldColumns, + { + title: 'body', + dataIndex: 'body', + key: 'body', + render: (field): ColumnTypeRender> => ({ + props: { + style: defaultTableStyle, + }, + children: ( + + ), + }), + }, + ]; + }, [fields, linesPerRow, onClickExpand]); + + return { columns, dataSource: flattenLogData }; +}; diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index ef0185c39968..8db0f7d2973b 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -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 }), diff --git a/frontend/src/constants/queryBuilderQueryNames.ts b/frontend/src/constants/queryBuilderQueryNames.ts index 5a9e9dbfe9a2..b3ee34cf8941 100644 --- a/frontend/src/constants/queryBuilderQueryNames.ts +++ b/frontend/src/constants/queryBuilderQueryNames.ts @@ -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 = { + compositeQuery: 'compositeQuery', + panelTypes: 'panelTypes', + pageSize: 'pageSize', + viewMode: 'viewMode', + selectedFields: 'selectedFields', + linesPerRow: 'linesPerRow', +}; diff --git a/frontend/src/container/Controls/config.ts b/frontend/src/container/Controls/config.ts index cc0378c54623..51d85207f89c 100644 --- a/frontend/src/container/Controls/config.ts +++ b/frontend/src/container/Controls/config.ts @@ -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', }; diff --git a/frontend/src/container/ExplorerControlPanel/ExplorerControlPanel.interfaces.ts b/frontend/src/container/ExplorerControlPanel/ExplorerControlPanel.interfaces.ts new file mode 100644 index 000000000000..15470012590f --- /dev/null +++ b/frontend/src/container/ExplorerControlPanel/ExplorerControlPanel.interfaces.ts @@ -0,0 +1,7 @@ +import { OptionsMenuConfig } from 'container/OptionsMenu/types'; + +export type ExplorerControlPanelProps = { + isShowPageSize: boolean; + isLoading: boolean; + optionsMenuConfig?: OptionsMenuConfig; +}; diff --git a/frontend/src/container/ExplorerControlPanel/index.tsx b/frontend/src/container/ExplorerControlPanel/index.tsx new file mode 100644 index 000000000000..4d6acb857172 --- /dev/null +++ b/frontend/src/container/ExplorerControlPanel/index.tsx @@ -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 ( + + + {optionsMenuConfig && ( + + + + )} + + + + + + ); +} + +export default ExplorerControlPanel; diff --git a/frontend/src/container/ExplorerControlPanel/styles.ts b/frontend/src/container/ExplorerControlPanel/styles.ts new file mode 100644 index 000000000000..0c9a799fef1f --- /dev/null +++ b/frontend/src/container/ExplorerControlPanel/styles.ts @@ -0,0 +1,5 @@ +import styled from 'styled-components'; + +export const ContainerStyled = styled.div` + margin-bottom: 0.3rem; +`; diff --git a/frontend/src/container/ExportPanel/index.tsx b/frontend/src/container/ExportPanel/index.tsx index 087b17fe72dc..35ac124faead 100644 --- a/frontend/src/container/ExportPanel/index.tsx +++ b/frontend/src/container/ExportPanel/index.tsx @@ -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]); diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx b/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx index a68d48ef8d18..0e950418b701 100644 --- a/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx +++ b/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx @@ -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]); diff --git a/frontend/src/container/ListAlertRules/ListAlert.tsx b/frontend/src/container/ListAlertRules/ListAlert.tsx index 76843be3066a..b1bfc7f72506 100644 --- a/frontend/src/container/ListAlertRules/ListAlert.tsx +++ b/frontend/src/container/ListAlertRules/ListAlert.tsx @@ -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); diff --git a/frontend/src/container/LogControls/index.tsx b/frontend/src/container/LogControls/index.tsx index d0dfe4f27b5b..7c3de3fef3ad 100644 --- a/frontend/src/container/LogControls/index.tsx +++ b/frontend/src/container/LogControls/index.tsx @@ -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], ); diff --git a/frontend/src/container/LogDetailedView/index.tsx b/frontend/src/container/LogDetailedView/index.tsx index 47f098244870..3860d657f0fe 100644 --- a/frontend/src/container/LogDetailedView/index.tsx +++ b/frontend/src/container/LogDetailedView/index.tsx @@ -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( (state) => state.logs, @@ -23,33 +20,7 @@ function LogDetailedView(): JSX.Element { }); }; - const items = [ - { - label: 'Table', - key: '1', - children: detailedLog && , - }, - { - label: 'JSON', - key: '2', - children: detailedLog && , - }, - ]; - - return ( - - - - ); + return ; } export default LogDetailedView; diff --git a/frontend/src/container/LogExplorerDetailedView/LogExplorerDetailedView.interfaces.ts b/frontend/src/container/LogExplorerDetailedView/LogExplorerDetailedView.interfaces.ts new file mode 100644 index 000000000000..ca9620657fbd --- /dev/null +++ b/frontend/src/container/LogExplorerDetailedView/LogExplorerDetailedView.interfaces.ts @@ -0,0 +1,6 @@ +import { ILog } from 'types/api/logs/log'; + +export type LogExplorerDetailedViewProps = { + log: ILog | null; + onClose: () => void; +}; diff --git a/frontend/src/container/LogExplorerDetailedView/index.tsx b/frontend/src/container/LogExplorerDetailedView/index.tsx new file mode 100644 index 000000000000..1a573f2ec8b2 --- /dev/null +++ b/frontend/src/container/LogExplorerDetailedView/index.tsx @@ -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 ; +} + +export default LogExplorerDetailedView; diff --git a/frontend/src/container/LogsExplorerChart/LogsExplorerChart.interfaces.ts b/frontend/src/container/LogsExplorerChart/LogsExplorerChart.interfaces.ts new file mode 100644 index 000000000000..6df4bacee30c --- /dev/null +++ b/frontend/src/container/LogsExplorerChart/LogsExplorerChart.interfaces.ts @@ -0,0 +1,6 @@ +import { QueryData } from 'types/api/widgets/getQuery'; + +export type LogsExplorerChartProps = { + data: QueryData[]; + isLoading: boolean; +}; diff --git a/frontend/src/container/LogsExplorerChart/index.tsx b/frontend/src/container/LogsExplorerChart/index.tsx index 02cc7606d763..2e85d9c5ded6 100644 --- a/frontend/src/container/LogsExplorerChart/index.tsx +++ b/frontend/src/container/LogsExplorerChart/index.tsx @@ -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['createDataset'] = ( + element, + index, + allLabels, + ) => ({ + label: allLabels[index], + data: element, + backgroundColor: colors[index % colors.length] || 'red', + borderColor: colors[index % colors.length] || 'red', + }); - const { selectedTime } = useSelector( - (state) => state.globalTime, + const graphData = useMemo( + () => + 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 ( - {isFetching ? ( + {isLoading ? ( ) : ( ( + {children} +); + +// eslint-disable-next-line react/function-component-definition +const CustomTableRow: TableComponents['TableRow'] = ({ + children, + context, + ...props + // eslint-disable-next-line react/jsx-props-no-spreading +}) => {children}; + +function InfinityTable({ + tableViewProps, + infitiyTableProps, +}: InfinityTableProps): JSX.Element | null { + const { onEndReached } = infitiyTableProps; + const { dataSource, columns } = useTableView(tableViewProps); + + const itemContent = useCallback( + (index: number, log: Record): JSX.Element => ( + <> + {columns.map((column) => { + if (!column.render) return ; + + const element: ColumnTypeRender> = column.render( + log[column.key as keyof Record], + log, + index, + ); + + const elementWithChildren = element as Exclude< + ColumnTypeRender>, + ReactNode + >; + + const children = elementWithChildren.children as ReactElement; + const props = elementWithChildren.props as Record; + + return ( + + {cloneElement(children, props)} + + ); + })} + + ), + [columns], + ); + + const tableHeader = useCallback( + () => ( + + {columns.map((column) => ( + + {column.title as string} + + ))} + + ), + [columns], + ); + + return ( + + ); +} + +export default InfinityTable; diff --git a/frontend/src/container/LogsExplorerList/InfinityTableView/styles.ts b/frontend/src/container/LogsExplorerList/InfinityTableView/styles.ts new file mode 100644 index 000000000000..a45d2d6b02e9 --- /dev/null +++ b/frontend/src/container/LogsExplorerList/InfinityTableView/styles.ts @@ -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; + } +`; diff --git a/frontend/src/container/LogsExplorerList/InfinityTableView/types.ts b/frontend/src/container/LogsExplorerList/InfinityTableView/types.ts new file mode 100644 index 000000000000..abd32a8c2306 --- /dev/null +++ b/frontend/src/container/LogsExplorerList/InfinityTableView/types.ts @@ -0,0 +1,8 @@ +import { LogsTableViewProps } from 'components/Logs/TableView/types'; + +export type InfinityTableProps = { + tableViewProps: LogsTableViewProps; + infitiyTableProps: { + onEndReached: (index: number) => void; + }; +}; diff --git a/frontend/src/container/LogsExplorerList/LogsExplorerList.interfaces.ts b/frontend/src/container/LogsExplorerList/LogsExplorerList.interfaces.ts index 4d0a534ad20a..b238031ac049 100644 --- a/frontend/src/container/LogsExplorerList/LogsExplorerList.interfaces.ts +++ b/frontend/src/container/LogsExplorerList/LogsExplorerList.interfaces.ts @@ -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; +}; diff --git a/frontend/src/container/LogsExplorerList/index.tsx b/frontend/src/container/LogsExplorerList/index.tsx index cacc611f9005..de537036f8f9 100644 --- a/frontend/src/container/LogsExplorerList/index.tsx +++ b/frontend/src/container/LogsExplorerList/index.tsx @@ -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 ; +} function LogsExplorerList({ - data, isLoading, + currentStagedQueryData, + logs, + onOpenDetailedView, + onEndReached, + onExpand, }: LogsExplorerListProps): JSX.Element { - const [viewMode] = useState('raw'); - const [linesPerRow] = useState(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 ( {}} + linesPerRow={options.maxLines} + onClickExpand={onExpand} /> ); } - return ; + return ( + + ); }, - [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 ( - {}} + ); } return ( - + ); - }, [getItemContent, linesPerRow, logs, viewMode]); - - if (isLoading) { - return ; - } + }, [ + isLoading, + logs, + options.format, + options.maxLines, + onEndReached, + getItemContent, + selectedFields, + onExpand, + ]); return ( - - {viewMode !== 'table' && ( + <> + + {options.format !== 'table' && ( Event )} - {logs.length === 0 && No logs lines found} - - {renderContent} - + {renderContent} + ); } diff --git a/frontend/src/container/LogsExplorerList/styles.ts b/frontend/src/container/LogsExplorerList/styles.ts new file mode 100644 index 000000000000..1c3dde6466c4 --- /dev/null +++ b/frontend/src/container/LogsExplorerList/styles.ts @@ -0,0 +1,6 @@ +import styled from 'styled-components'; + +export const InfinityWrapperStyled = styled.div` + min-height: 40rem; + display: flex; +`; diff --git a/frontend/src/container/LogsExplorerList/utils.ts b/frontend/src/container/LogsExplorerList/utils.ts new file mode 100644 index 000000000000..6058ac4b4f5e --- /dev/null +++ b/frontend/src/container/LogsExplorerList/utils.ts @@ -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, + })); diff --git a/frontend/src/container/LogsExplorerTable/index.tsx b/frontend/src/container/LogsExplorerTable/index.tsx index 40aefcf5e5b1..bbcb2aa99b55 100644 --- a/frontend/src/container/LogsExplorerTable/index.tsx +++ b/frontend/src/container/LogsExplorerTable/index.tsx @@ -6,8 +6,8 @@ import { memo } from 'react'; import { LogsExplorerTableProps } from './LogsExplorerTable.interfaces'; function LogsExplorerTable({ - isLoading, data, + isLoading, }: LogsExplorerTableProps): JSX.Element { const { stagedQuery } = useQueryBuilder(); diff --git a/frontend/src/container/LogsExplorerViews/index.tsx b/frontend/src/container/LogsExplorerViews/index.tsx index 6229a3fe22e3..a9a8d7089487 100644 --- a/frontend/src/container/LogsExplorerViews/index.tsx +++ b/frontend/src/container/LogsExplorerViews/index.tsx @@ -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( - (state) => state.globalTime, - ); + // State + const [activeLog, setActiveLog] = useState(null); + const [page, setPage] = useState(1); + const [logs, setLogs] = useState([]); + const [requestData, setRequestData] = useState(null); - 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, - }, - ); + 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', + ); + + 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( - () => [ - { - label: 'List View', - key: PANEL_TYPES.LIST, - disabled: isMultipleQueries || isGroupByExist, - children: , - }, - { - label: 'TimeSeries', - key: PANEL_TYPES.TIME_SERIES, - children: ( - - ), - }, - { - label: 'Table', - key: PANEL_TYPES.TABLE, - children: , - }, - ], - [isMultipleQueries, isGroupByExist, currentData, isFetching, data, isError], + const { data, isFetching, isError } = useGetExplorerQueryRange( + requestData, + panelType, + { + keepPreviousData: true, + enabled: !isLimit, + }, ); + 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: ( + + ), + }, + { + label: 'TimeSeries', + key: PANEL_TYPES.TIME_SERIES, + children: ( + + ), + }, + { + label: 'Table', + key: PANEL_TYPES.TABLE, + children: ( + + ), + }, + ], + [ + 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 ( -
+ <> + -
+ + ); } diff --git a/frontend/src/container/LogsTable/index.tsx b/frontend/src/container/LogsTable/index.tsx index 464258877ec4..b514aa3ee848 100644 --- a/frontend/src/container/LogsTable/index.tsx +++ b/frontend/src/container/LogsTable/index.tsx @@ -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 ; + return ( + + ); }, - [logs, linesPerRow, viewMode, onClickExpand], + [ + logs, + viewMode, + selected, + handleOpenDetailedView, + linesPerRow, + onClickExpand, + ], ); const renderContent = useMemo(() => { diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx index f490f1f23e00..8ede8520b78e 100644 --- a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx +++ b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx @@ -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), )}`, ); diff --git a/frontend/src/container/OptionsMenu/FormatField/index.tsx b/frontend/src/container/OptionsMenu/FormatField/index.tsx index c4d88bacbea6..3b82631a5d08 100644 --- a/frontend/src/container/OptionsMenu/FormatField/index.tsx +++ b/frontend/src/container/OptionsMenu/FormatField/index.tsx @@ -18,9 +18,9 @@ function FormatField({ config }: FormatFieldProps): JSX.Element | null { value={config.value} onChange={config.onChange} > - {t('options_menu.row')} - {t('options_menu.default')} - {t('options_menu.column')} + {t('options_menu.row')} + {t('options_menu.default')} + {t('options_menu.column')} ); diff --git a/frontend/src/container/OptionsMenu/constants.ts b/frontend/src/container/OptionsMenu/constants.ts index 9b9d41709d4f..b1e54636865a 100644 --- a/frontend/src/container/OptionsMenu/constants.ts +++ b/frontend/src/container/OptionsMenu/constants.ts @@ -4,6 +4,6 @@ export const URL_OPTIONS = 'options'; export const defaultOptionsQuery: OptionsQuery = { selectColumns: [], - maxLines: 0, - format: 'default', + maxLines: 2, + format: 'list', }; diff --git a/frontend/src/container/OptionsMenu/types.ts b/frontend/src/container/OptionsMenu/types.ts index f557e1dbe339..e7fdf89f9758 100644 --- a/frontend/src/container/OptionsMenu/types.ts +++ b/frontend/src/container/OptionsMenu/types.ts @@ -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 diff --git a/frontend/src/container/OptionsMenu/useOptionsMenu.ts b/frontend/src/container/OptionsMenu/useOptionsMenu.ts index f39557f9f738..578fd84e2686 100644 --- a/frontend/src/container/OptionsMenu/useOptionsMenu.ts +++ b/frontend/src/container/OptionsMenu/useOptionsMenu.ts @@ -35,16 +35,17 @@ const useOptionsMenu = ({ query: optionsQuery, queryData: optionsQueryData, redirectWithQuery: redirectWithOptionsData, - } = useUrlQueryData(URL_OPTIONS); + } = useUrlQueryData(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 = useMemo( diff --git a/frontend/src/container/PageSizeSelect/PageSizeSelect.interfaces.ts b/frontend/src/container/PageSizeSelect/PageSizeSelect.interfaces.ts new file mode 100644 index 000000000000..dc881ebacc12 --- /dev/null +++ b/frontend/src/container/PageSizeSelect/PageSizeSelect.interfaces.ts @@ -0,0 +1,4 @@ +export type PageSizeSelectProps = { + isLoading: boolean; + isShow: boolean; +}; diff --git a/frontend/src/container/PageSizeSelect/index.tsx b/frontend/src/container/PageSizeSelect/index.tsx new file mode 100644 index 000000000000..835549ddaa9a --- /dev/null +++ b/frontend/src/container/PageSizeSelect/index.tsx @@ -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( + queryParamNamesMap.pageSize, + ITEMS_PER_PAGE_OPTIONS[0], + ); + + const handleChangePageSize = useCallback( + (value: number) => { + redirectWithQuery(value); + }, + [redirectWithQuery], + ); + + if (!isShow) return null; + + return ( + +
+ + + + ); +} + +export default PageSizeSelect; diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts index ab61c94f22ff..6135e0b67059 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts +++ b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts @@ -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>; }; diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.tsx b/frontend/src/container/QueryBuilder/QueryBuilder.tsx index 464c4599c107..4aefedb0227a 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.tsx +++ b/frontend/src/container/QueryBuilder/QueryBuilder.tsx @@ -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} /> ))} diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts b/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts index 414e61678b46..8cee2d9dad1a 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts +++ b/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts @@ -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; diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx index d0c5f126a5db..dd42404c6d47 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx +++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx @@ -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 ? ( + + + + + + + + + ) : 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({ )} - - - - - - - - - - + {renderAggregateEveryFilter()} ); } @@ -179,19 +186,7 @@ export const Query = memo(function Query({ - - - - - - - - - - + {renderAggregateEveryFilter()} ); } @@ -230,21 +225,7 @@ export const Query = memo(function Query({ - {panelType !== PANEL_TYPES.LIST && ( - - - - - - - - - - - )} + {renderAggregateEveryFilter()} ); } @@ -253,10 +234,10 @@ export const Query = memo(function Query({ panelType, query, isMetricsDataSource, - handleChangeAggregateEvery, handleChangeHavingFilter, handleChangeLimit, handleChangeOrderByKeys, + renderAggregateEveryFilter, ]); return ( diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx b/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx index f85c72dc349d..3994c6c73b65 100644 --- a/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx @@ -27,7 +27,7 @@ export function OrderByFilter({ }: OrderByFilterProps): JSX.Element { const [searchText, setSearchText] = useState(''); const [selectedValue, setSelectedValue] = useState( - transformToOrderByStringValues(query.orderBy) || [], + transformToOrderByStringValues(query.orderBy), ); const { data, isFetching } = useQuery( diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts b/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts index 540674dec240..1d415ec2d512 100644 --- a/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts +++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts @@ -8,11 +8,25 @@ export const orderByValueDelimiter = '|'; export const transformToOrderByStringValues = ( orderBy: OrderByPayload[], -): IOption[] => - orderBy.map((item) => ({ - label: `${item.columnName} ${item.order}`, - value: `${item.columnName}${orderByValueDelimiter}${item.order}`, - })); +): IOption[] => { + const prepareSelectedValue: IOption[] = orderBy.reduce( + (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[], diff --git a/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts b/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts index 894167815b84..d9060539cc6b 100644 --- a/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts +++ b/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts @@ -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 { diff --git a/frontend/src/hooks/queryBuilder/useGetExplorerQueryRange.ts b/frontend/src/hooks/queryBuilder/useGetExplorerQueryRange.ts new file mode 100644 index 000000000000..15ea31855622 --- /dev/null +++ b/frontend/src/hooks/queryBuilder/useGetExplorerQueryRange.ts @@ -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, Error>, +): UseQueryResult, 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, + }, + ); +}; diff --git a/frontend/src/hooks/queryBuilder/useGetPanelTypesQueryParam.ts b/frontend/src/hooks/queryBuilder/useGetPanelTypesQueryParam.ts index 06cc11829a1b..560790e574e4 100644 --- a/frontend/src/hooks/queryBuilder/useGetPanelTypesQueryParam.ts +++ b/frontend/src/hooks/queryBuilder/useGetPanelTypesQueryParam.ts @@ -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 = ( 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]); diff --git a/frontend/src/hooks/queryBuilder/useQueryOperations.ts b/frontend/src/hooks/queryBuilder/useQueryOperations.ts index 02af0118d02c..d2cc94781972 100644 --- a/frontend/src/hooks/queryBuilder/useQueryOperations.ts +++ b/frontend/src/hooks/queryBuilder/useQueryOperations.ts @@ -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); + const result: string[] = mapOfFilters[dataSource].reduce( + (acc, item) => { + if (inactiveFilters && inactiveFilters[item.field]) { + return acc; + } - if (panelType === PANEL_TYPES.LIST) { - return listOfFilters.filter((filter) => filter !== 'Aggregation interval'); - } + acc.push(item.text); - return listOfFilters; + return acc; + }, + [], + ); + + return result; }, - [panelType], + + [inactiveFilters], ); const handleChangeAggregatorAttribute = useCallback( diff --git a/frontend/src/lib/explorer/getExplorerChartData.ts b/frontend/src/lib/explorer/getExplorerChartData.ts deleted file mode 100644 index 152b72f9ac6d..000000000000 --- a/frontend/src/lib/explorer/getExplorerChartData.ts +++ /dev/null @@ -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(); - - 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; -}; diff --git a/frontend/src/lib/getChartData.ts b/frontend/src/lib/getChartData.ts index 8d32d969c229..7c57d2d721ba 100644 --- a/frontend/src/lib/getChartData.ts +++ b/frontend/src/lib/getChartData.ts @@ -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(); 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, - label: allLabels[index], - borderWidth: 1.5, - spanGaps: true, - animations: false, - borderColor: colors[index % colors.length] || 'red', - showLine: true, - pointRadius: 0, - })), + 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, + 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; diff --git a/frontend/src/lib/newQueryBuilder/getPaginationQueryData.ts b/frontend/src/lib/newQueryBuilder/getPaginationQueryData.ts new file mode 100644 index 000000000000..d2a36392c8e5 --- /dev/null +++ b/frontend/src/lib/newQueryBuilder/getPaginationQueryData.ts @@ -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; + +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 = { + filters: orderByTimestamp ? tagFilters : updatedFilters, + ...queryProps, + }; + + return { ...currentStagedQueryData, ...chunkOfQueryData }; +}; diff --git a/frontend/src/lib/query/createTableColumnsFromQuery.ts b/frontend/src/lib/query/createTableColumnsFromQuery.ts index 653a859ba858..0637917efda0 100644 --- a/frontend/src/lib/query/createTableColumnsFromQuery.ts +++ b/frontend/src/lib/query/createTableColumnsFromQuery.ts @@ -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); diff --git a/frontend/src/pages/LogsExplorer/index.tsx b/frontend/src/pages/LogsExplorer/index.tsx index 519538365e63..98278db07636 100644 --- a/frontend/src/pages/LogsExplorer/index.tsx +++ b/frontend/src/pages/LogsExplorer/index.tsx @@ -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( - initialQueriesMap.logs, - PANEL_TYPES.LIST, - DataSource.LOGS, - ), - [updateAllQueriesOperators], - ); + const defaultValue = useMemo(() => { + const updatedQuery = updateAllQueriesOperators( + initialQueriesMap.logs, + PANEL_TYPES.LIST, + DataSource.LOGS, + ); + 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 ( @@ -35,6 +45,7 @@ function LogsExplorer(): JSX.Element { - diff --git a/frontend/src/pages/LogsExplorer/utils.ts b/frontend/src/pages/LogsExplorer/utils.ts new file mode 100644 index 000000000000..13e8a29a4f7f --- /dev/null +++ b/frontend/src/pages/LogsExplorer/utils.ts @@ -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' }], + })), + }, +}); diff --git a/frontend/src/pages/TracesExplorer/index.tsx b/frontend/src/pages/TracesExplorer/index.tsx index 179a9a93e0f4..3bcd8f72442f 100644 --- a/frontend/src/pages/TracesExplorer/index.tsx +++ b/frontend/src/pages/TracesExplorer/index.tsx @@ -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, diff --git a/frontend/src/providers/QueryBuilder.tsx b/frontend/src/providers/QueryBuilder.tsx index 7cb43a1a7e87..e5578f18f812 100644 --- a/frontend/src/providers/QueryBuilder.tsx +++ b/frontend/src/providers/QueryBuilder.tsx @@ -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)), ); diff --git a/frontend/src/store/actions/dashboard/saveDashboard.ts b/frontend/src/store/actions/dashboard/saveDashboard.ts index 14e62fedac09..827a11fc6418 100644 --- a/frontend/src/store/actions/dashboard/saveDashboard.ts +++ b/frontend/src/store/actions/dashboard/saveDashboard.ts @@ -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( diff --git a/frontend/src/types/api/logs/log.ts b/frontend/src/types/api/logs/log.ts index eb862daa9c9c..a7c00885ffdb 100644 --- a/frontend/src/types/api/logs/log.ts +++ b/frontend/src/types/api/logs/log.ts @@ -1,6 +1,6 @@ export interface ILog { date: string; - timestamp: number; + timestamp: number | string; id: string; traceId: string; spanId: string; diff --git a/frontend/src/types/api/queryBuilder/queryBuilderData.ts b/frontend/src/types/api/queryBuilder/queryBuilderData.ts index ee9cfb6aef61..b16aac3b3cca 100644 --- a/frontend/src/types/api/queryBuilder/queryBuilderData.ts +++ b/frontend/src/types/api/queryBuilder/queryBuilderData.ts @@ -59,6 +59,8 @@ export type IBuilderQuery = { orderBy: OrderByPayload[]; reduceTo: ReduceOperators; legend: string; + pageSize?: number; + offset?: number; }; export interface IClickHouseQuery { diff --git a/frontend/src/types/api/widgets/getQuery.ts b/frontend/src/types/api/widgets/getQuery.ts index 0b36af154156..f60eebc0bb8d 100644 --- a/frontend/src/types/api/widgets/getQuery.ts +++ b/frontend/src/types/api/widgets/getQuery.ts @@ -5,7 +5,10 @@ export interface PayloadProps { result: QueryData[]; } -export type ListItem = { timestamp: string; data: Omit }; +export type ListItem = { + timestamp: string; + data: Omit; +}; export interface QueryData { metric: { diff --git a/frontend/src/types/common/operations.types.ts b/frontend/src/types/common/operations.types.ts index d5872c8bbeb9..2664d95ae720 100644 --- a/frontend/src/types/common/operations.types.ts +++ b/frontend/src/types/common/operations.types.ts @@ -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; +type UseQueryOperationsParams = Pick & + Pick; export type HandleChangeQueryData = < Key extends keyof IBuilderQuery,
Empty