From bd18eee662d0097bc4dbf9717c7f78b37fb57eab Mon Sep 17 00:00:00 2001 From: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:19:53 +0300 Subject: [PATCH] Fix/query builder updating (#2962) * feat: add dynamic table based on query * fix: group by repeating * fix: change view when groupBy exist in the list * feat: add list view for log explorer * fix: query builder updating * fix: table scroll * fix: filters for explorer page (#2959) --------- Co-authored-by: Prashant Shahi --- .../src/container/FormAlertRules/index.tsx | 2 +- .../src/container/LogsExplorerChart/index.ts | 1 - .../{LogsExplorerChart.tsx => index.tsx} | 22 +- .../LogsExplorerList.interfaces.ts | 3 + .../src/container/LogsExplorerList/index.tsx | 117 +++++++++ .../LogsExplorerTable.interfaces.ts | 6 + .../LogsExplorerTable/LogsExplorerTable.tsx | 44 ---- .../src/container/LogsExplorerTable/index.ts | 1 - .../src/container/LogsExplorerTable/index.tsx | 23 ++ .../LogsExplorerViews/LogsExplorerViews.tsx | 87 ------- .../src/container/LogsExplorerViews/index.ts | 1 - .../src/container/LogsExplorerViews/index.tsx | 124 ++++++++++ .../LeftContainer/QuerySection/index.tsx | 2 +- .../container/QueryBuilder/QueryBuilder.tsx | 30 ++- .../queryBuilder/useGetWidgetQueryRange.ts | 10 +- .../hooks/queryBuilder/useQueryOperations.ts | 22 +- .../hooks/queryBuilder/useShareBuilderUrl.ts | 10 +- frontend/src/pages/LogsExplorer/index.tsx | 19 +- frontend/src/pages/TracesExplorer/index.tsx | 2 +- frontend/src/providers/QueryBuilder.tsx | 223 +++++++++++------- frontend/src/types/api/widgets/getQuery.ts | 6 +- frontend/src/types/common/queryBuilder.ts | 14 +- 22 files changed, 491 insertions(+), 278 deletions(-) delete mode 100644 frontend/src/container/LogsExplorerChart/index.ts rename frontend/src/container/LogsExplorerChart/{LogsExplorerChart.tsx => index.tsx} (75%) create mode 100644 frontend/src/container/LogsExplorerList/LogsExplorerList.interfaces.ts create mode 100644 frontend/src/container/LogsExplorerList/index.tsx create mode 100644 frontend/src/container/LogsExplorerTable/LogsExplorerTable.interfaces.ts delete mode 100644 frontend/src/container/LogsExplorerTable/LogsExplorerTable.tsx delete mode 100644 frontend/src/container/LogsExplorerTable/index.ts create mode 100644 frontend/src/container/LogsExplorerTable/index.tsx delete mode 100644 frontend/src/container/LogsExplorerViews/LogsExplorerViews.tsx delete mode 100644 frontend/src/container/LogsExplorerViews/index.ts create mode 100644 frontend/src/container/LogsExplorerViews/index.tsx diff --git a/frontend/src/container/FormAlertRules/index.tsx b/frontend/src/container/FormAlertRules/index.tsx index 9db261afd0..26a0830226 100644 --- a/frontend/src/container/FormAlertRules/index.tsx +++ b/frontend/src/container/FormAlertRules/index.tsx @@ -70,7 +70,7 @@ function FormAlertRules({ const sq = useMemo(() => mapQueryDataFromApi(initQuery), [initQuery]); - useShareBuilderUrl({ defaultValue: sq }); + useShareBuilderUrl(sq); useEffect(() => { setAlertDef(initialValue); diff --git a/frontend/src/container/LogsExplorerChart/index.ts b/frontend/src/container/LogsExplorerChart/index.ts deleted file mode 100644 index 48d9469dba..0000000000 --- a/frontend/src/container/LogsExplorerChart/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { LogsExplorerChart } from './LogsExplorerChart'; diff --git a/frontend/src/container/LogsExplorerChart/LogsExplorerChart.tsx b/frontend/src/container/LogsExplorerChart/index.tsx similarity index 75% rename from frontend/src/container/LogsExplorerChart/LogsExplorerChart.tsx rename to frontend/src/container/LogsExplorerChart/index.tsx index 6c2307efc2..02cc7606d7 100644 --- a/frontend/src/container/LogsExplorerChart/LogsExplorerChart.tsx +++ b/frontend/src/container/LogsExplorerChart/index.tsx @@ -2,41 +2,33 @@ 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 { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { getExplorerChartData } from 'lib/explorer/getExplorerChartData'; -import { useMemo } from 'react'; +import { memo, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { GlobalReducer } from 'types/reducer/globalTime'; import { CardStyled } from './LogsExplorerChart.styled'; -export function LogsExplorerChart(): JSX.Element { - const { stagedQuery } = useQueryBuilder(); +function LogsExplorerChart(): JSX.Element { + const { stagedQuery, panelType, isEnabledQuery } = useQueryBuilder(); const { selectedTime } = useSelector( (state) => state.globalTime, ); - const panelTypeParam = useGetPanelTypesQueryParam(PANEL_TYPES.LIST); - const { data, isFetching } = useGetQueryRange( { query: stagedQuery || initialQueriesMap.metrics, - graphType: panelTypeParam, + graphType: panelType || PANEL_TYPES.LIST, globalSelectedInterval: selectedTime, selectedTime: 'GLOBAL_TIME', }, { - queryKey: [ - REACT_QUERY_KEY.GET_QUERY_RANGE, - selectedTime, - stagedQuery, - panelTypeParam, - ], - enabled: !!stagedQuery, + queryKey: [REACT_QUERY_KEY.GET_QUERY_RANGE, selectedTime, stagedQuery], + enabled: isEnabledQuery, }, ); @@ -64,3 +56,5 @@ export function LogsExplorerChart(): JSX.Element { ); } + +export default memo(LogsExplorerChart); diff --git a/frontend/src/container/LogsExplorerList/LogsExplorerList.interfaces.ts b/frontend/src/container/LogsExplorerList/LogsExplorerList.interfaces.ts new file mode 100644 index 0000000000..4d0a534ad2 --- /dev/null +++ b/frontend/src/container/LogsExplorerList/LogsExplorerList.interfaces.ts @@ -0,0 +1,3 @@ +import { QueryDataV3 } from 'types/api/widgets/getQuery'; + +export type LogsExplorerListProps = { data: QueryDataV3[]; isLoading: boolean }; diff --git a/frontend/src/container/LogsExplorerList/index.tsx b/frontend/src/container/LogsExplorerList/index.tsx new file mode 100644 index 0000000000..cacc611f90 --- /dev/null +++ b/frontend/src/container/LogsExplorerList/index.tsx @@ -0,0 +1,117 @@ +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 { contentStyle } from 'container/Trace/Search/config'; +import useFontFaceObserver from 'hooks/useFontObserver'; +import { memo, useCallback, useMemo, useState } from 'react'; +import { Virtuoso } from 'react-virtuoso'; +// interfaces +import { ILog } from 'types/api/logs/log'; + +import { LogsExplorerListProps } from './LogsExplorerList.interfaces'; + +function LogsExplorerList({ + data, + isLoading, +}: LogsExplorerListProps): JSX.Element { + const [viewMode] = useState('raw'); + const [linesPerRow] = useState(20); + + 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]); + + useFontFaceObserver( + [ + { + family: 'Fira Code', + weight: '300', + }, + ], + viewMode === 'raw', + { + timeout: 5000, + }, + ); + // TODO: implement here linesPerRow, mode like in useSelectedLogView + + const getItemContent = useCallback( + (index: number): JSX.Element => { + const log = logs[index]; + + if (viewMode === 'raw') { + return ( + {}} + /> + ); + } + + return ; + }, + [logs, linesPerRow, viewMode], + ); + + const renderContent = useMemo(() => { + if (viewMode === 'table') { + return ( + {}} + /> + ); + } + + return ( + + + + ); + }, [getItemContent, linesPerRow, logs, viewMode]); + + if (isLoading) { + return ; + } + + return ( + + {viewMode !== 'table' && ( + + Event + + )} + + {logs.length === 0 && No logs lines found} + + {renderContent} + + ); +} + +export default memo(LogsExplorerList); diff --git a/frontend/src/container/LogsExplorerTable/LogsExplorerTable.interfaces.ts b/frontend/src/container/LogsExplorerTable/LogsExplorerTable.interfaces.ts new file mode 100644 index 0000000000..f2e4c3e7d6 --- /dev/null +++ b/frontend/src/container/LogsExplorerTable/LogsExplorerTable.interfaces.ts @@ -0,0 +1,6 @@ +import { QueryDataV3 } from 'types/api/widgets/getQuery'; + +export type LogsExplorerTableProps = { + data: QueryDataV3[]; + isLoading: boolean; +}; diff --git a/frontend/src/container/LogsExplorerTable/LogsExplorerTable.tsx b/frontend/src/container/LogsExplorerTable/LogsExplorerTable.tsx deleted file mode 100644 index 1ac4510c56..0000000000 --- a/frontend/src/container/LogsExplorerTable/LogsExplorerTable.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; -import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; -import { QueryTable } from 'container/QueryTable'; -import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; -import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import { useSelector } from 'react-redux'; -import { AppState } from 'store/reducers'; -import { GlobalReducer } from 'types/reducer/globalTime'; - -export function LogsExplorerTable(): JSX.Element { - const { stagedQuery } = useQueryBuilder(); - - const { selectedTime } = useSelector( - (state) => state.globalTime, - ); - - const panelTypeParam = useGetPanelTypesQueryParam(PANEL_TYPES.LIST); - - const { data, isFetching } = useGetQueryRange( - { - query: stagedQuery || initialQueriesMap.metrics, - graphType: panelTypeParam, - globalSelectedInterval: selectedTime, - selectedTime: 'GLOBAL_TIME', - }, - { - queryKey: [ - REACT_QUERY_KEY.GET_QUERY_RANGE, - selectedTime, - stagedQuery, - panelTypeParam, - ], - enabled: !!stagedQuery, - }, - ); - return ( - - ); -} diff --git a/frontend/src/container/LogsExplorerTable/index.ts b/frontend/src/container/LogsExplorerTable/index.ts deleted file mode 100644 index 4a9b82e594..0000000000 --- a/frontend/src/container/LogsExplorerTable/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { LogsExplorerTable } from './LogsExplorerTable'; diff --git a/frontend/src/container/LogsExplorerTable/index.tsx b/frontend/src/container/LogsExplorerTable/index.tsx new file mode 100644 index 0000000000..40aefcf5e5 --- /dev/null +++ b/frontend/src/container/LogsExplorerTable/index.tsx @@ -0,0 +1,23 @@ +import { initialQueriesMap } from 'constants/queryBuilder'; +import { QueryTable } from 'container/QueryTable'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { memo } from 'react'; + +import { LogsExplorerTableProps } from './LogsExplorerTable.interfaces'; + +function LogsExplorerTable({ + isLoading, + data, +}: LogsExplorerTableProps): JSX.Element { + const { stagedQuery } = useQueryBuilder(); + + return ( + + ); +} + +export default memo(LogsExplorerTable); diff --git a/frontend/src/container/LogsExplorerViews/LogsExplorerViews.tsx b/frontend/src/container/LogsExplorerViews/LogsExplorerViews.tsx deleted file mode 100644 index 76765959b0..0000000000 --- a/frontend/src/container/LogsExplorerViews/LogsExplorerViews.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { TabsProps } from 'antd'; -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { PANEL_TYPES_QUERY } from 'constants/queryBuilderQueryNames'; -import { LogsExplorerTable } from 'container/LogsExplorerTable'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; -import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import useUrlQuery from 'hooks/useUrlQuery'; -import { useCallback, useEffect, useMemo } from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; - -import { TabsStyled } from './LogsExplorerViews.styled'; - -export function LogsExplorerViews(): JSX.Element { - const location = useLocation(); - const urlQuery = useUrlQuery(); - const history = useHistory(); - const { currentQuery } = useQueryBuilder(); - - const panelTypeParams = useGetPanelTypesQueryParam(PANEL_TYPES.LIST); - - const isMultipleQueries = useMemo( - () => - currentQuery.builder.queryData.length > 1 || - currentQuery.builder.queryFormulas.length > 0, - [currentQuery], - ); - - const isGroupByExist = useMemo(() => { - const groupByCount: number = currentQuery.builder.queryData.reduce( - (acc, query) => acc + query.groupBy.length, - 0, - ); - - return groupByCount > 0; - }, [currentQuery]); - - const tabsItems: TabsProps['items'] = useMemo( - () => [ - { - label: 'List View', - key: PANEL_TYPES.LIST, - disabled: isMultipleQueries || isGroupByExist, - }, - { label: 'TimeSeries', key: PANEL_TYPES.TIME_SERIES }, - { label: 'Table', key: PANEL_TYPES.TABLE, children: }, - ], - [isMultipleQueries, isGroupByExist], - ); - - const handleChangeView = useCallback( - (panelType: string) => { - urlQuery.set(PANEL_TYPES_QUERY, JSON.stringify(panelType) as GRAPH_TYPES); - const path = `${location.pathname}?${urlQuery}`; - - history.push(path); - }, - [history, location, urlQuery], - ); - - const currentTabKey = useMemo( - () => - Object.values(PANEL_TYPES).includes(panelTypeParams) - ? panelTypeParams - : PANEL_TYPES.LIST, - [panelTypeParams], - ); - - useEffect(() => { - const shouldChangeView = isMultipleQueries || isGroupByExist; - - if (panelTypeParams === 'list' && shouldChangeView) { - handleChangeView(PANEL_TYPES.TIME_SERIES); - } - }, [panelTypeParams, isMultipleQueries, isGroupByExist, handleChangeView]); - - return ( -
- -
- ); -} diff --git a/frontend/src/container/LogsExplorerViews/index.ts b/frontend/src/container/LogsExplorerViews/index.ts deleted file mode 100644 index 4a29ed0988..0000000000 --- a/frontend/src/container/LogsExplorerViews/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { LogsExplorerViews } from './LogsExplorerViews'; diff --git a/frontend/src/container/LogsExplorerViews/index.tsx b/frontend/src/container/LogsExplorerViews/index.tsx new file mode 100644 index 0000000000..f95c79d252 --- /dev/null +++ b/frontend/src/container/LogsExplorerViews/index.tsx @@ -0,0 +1,124 @@ +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 LogsExplorerList from 'container/LogsExplorerList'; +import LogsExplorerTable from 'container/LogsExplorerTable'; +import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; +import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; +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 { TabsStyled } from './LogsExplorerViews.styled'; + +function LogsExplorerViews(): JSX.Element { + const { + currentQuery, + stagedQuery, + panelType, + isEnabledQuery, + updateAllQueriesOperators, + redirectWithQueryBuilderData, + } = useQueryBuilder(); + + const { selectedTime } = useSelector( + (state) => state.globalTime, + ); + + 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 isMultipleQueries = useMemo( + () => + currentQuery.builder.queryData.length > 1 || + currentQuery.builder.queryFormulas.length > 0, + [currentQuery], + ); + + const isGroupByExist = useMemo(() => { + const groupByCount: number = currentQuery.builder.queryData.reduce( + (acc, query) => acc + query.groupBy.length, + 0, + ); + + return groupByCount > 0; + }, [currentQuery]); + + const currentData = useMemo( + () => data?.payload.data.newResult.data.result || [], + [data], + ); + + const tabsItems: TabsProps['items'] = useMemo( + () => [ + { + label: 'List View', + key: PANEL_TYPES.LIST, + disabled: isMultipleQueries || isGroupByExist, + children: , + }, + { label: 'TimeSeries', key: PANEL_TYPES.TIME_SERIES }, + { + label: 'Table', + key: PANEL_TYPES.TABLE, + children: , + }, + ], + [isMultipleQueries, isGroupByExist, currentData, isFetching], + ); + + const handleChangeView = useCallback( + (newPanelType: string) => { + if (newPanelType === panelType) return; + + const query = updateAllQueriesOperators( + currentQuery, + newPanelType as GRAPH_TYPES, + DataSource.LOGS, + ); + + redirectWithQueryBuilderData(query, { [PANEL_TYPES_QUERY]: newPanelType }); + }, + [ + currentQuery, + panelType, + updateAllQueriesOperators, + redirectWithQueryBuilderData, + ], + ); + + useEffect(() => { + const shouldChangeView = isMultipleQueries || isGroupByExist; + + if (panelType === 'list' && shouldChangeView) { + handleChangeView(PANEL_TYPES.TIME_SERIES); + } + }, [panelType, isMultipleQueries, isGroupByExist, handleChangeView]); + + return ( +
+ +
+ ); +} + +export default memo(LogsExplorerViews); diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx index d3e52120b3..3f36301098 100644 --- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx @@ -58,7 +58,7 @@ function QuerySection({ const { query } = selectedWidget; - useShareBuilderUrl({ defaultValue: query }); + useShareBuilderUrl(query); const handleStageQuery = useCallback( (updatedQuery: Query): void => { diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.tsx b/frontend/src/container/QueryBuilder/QueryBuilder.tsx index dae8717920..8739d6281b 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.tsx +++ b/frontend/src/container/QueryBuilder/QueryBuilder.tsx @@ -15,26 +15,36 @@ import { ActionsWrapperStyled } from './QueryBuilder.styled'; export const QueryBuilder = memo(function QueryBuilder({ config, - panelType, + panelType: newPanelType, actions, }: QueryBuilderProps): JSX.Element { const { currentQuery, - setupInitialDataSource, addNewBuilderQuery, addNewFormula, - handleSetPanelType, + handleSetConfig, + panelType, + initialDataSource, } = useQueryBuilder(); - useEffect(() => { - if (config && config.queryVariant === 'static') { - setupInitialDataSource(config.initialDataSource); - } - }, [config, setupInitialDataSource]); + const currentDataSource = useMemo( + () => + (config && config.queryVariant === 'static' && config.initialDataSource) || + null, + [config], + ); useEffect(() => { - handleSetPanelType(panelType); - }, [handleSetPanelType, panelType]); + if (currentDataSource !== initialDataSource || newPanelType !== panelType) { + handleSetConfig(newPanelType, currentDataSource); + } + }, [ + handleSetConfig, + panelType, + initialDataSource, + currentDataSource, + newPanelType, + ]); const isDisabledQueryButton = useMemo( () => currentQuery.builder.queryData.length >= MAX_QUERIES, diff --git a/frontend/src/hooks/queryBuilder/useGetWidgetQueryRange.ts b/frontend/src/hooks/queryBuilder/useGetWidgetQueryRange.ts index 289e6c9f1a..309dda7a6c 100644 --- a/frontend/src/hooks/queryBuilder/useGetWidgetQueryRange.ts +++ b/frontend/src/hooks/queryBuilder/useGetWidgetQueryRange.ts @@ -1,6 +1,5 @@ -import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames'; +import { initialQueriesMap } from 'constants/queryBuilder'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; -import useUrlQuery from 'hooks/useUrlQuery'; import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables'; import { UseQueryOptions, UseQueryResult } from 'react-query'; import { useSelector } from 'react-redux'; @@ -10,6 +9,7 @@ import { SuccessResponse } from 'types/api'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { GlobalReducer } from 'types/reducer/globalTime'; +import { useGetCompositeQueryParam } from './useGetCompositeQueryParam'; import { useGetQueryRange } from './useGetQueryRange'; export const useGetWidgetQueryRange = ( @@ -19,21 +19,19 @@ export const useGetWidgetQueryRange = ( }: Pick, options?: UseQueryOptions, Error>, ): UseQueryResult, Error> => { - const urlQuery = useUrlQuery(); - const { selectedTime: globalSelectedInterval } = useSelector< AppState, GlobalReducer >((state) => state.globalTime); - const compositeQuery = urlQuery.get(COMPOSITE_QUERY); + const compositeQuery = useGetCompositeQueryParam(); return useGetQueryRange( { graphType, selectedTime, globalSelectedInterval, - query: JSON.parse(compositeQuery || '{}'), + query: compositeQuery || initialQueriesMap.metrics, variables: getDashboardVariables(), }, { diff --git a/frontend/src/hooks/queryBuilder/useQueryOperations.ts b/frontend/src/hooks/queryBuilder/useQueryOperations.ts index b1d2f35c18..00cbdebcff 100644 --- a/frontend/src/hooks/queryBuilder/useQueryOperations.ts +++ b/frontend/src/hooks/queryBuilder/useQueryOperations.ts @@ -2,6 +2,7 @@ import { initialAutocompleteData, initialQueryBuilderFormValuesMap, mapOfFilters, + PANEL_TYPES, } from 'constants/queryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { getOperatorsBySourceAndPanelType } from 'lib/newQueryBuilder/getOperatorsBySourceAndPanelType'; @@ -78,7 +79,7 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => { (nextSource: DataSource): void => { const newOperators = getOperatorsBySourceAndPanelType({ dataSource: nextSource, - panelType, + panelType: panelType || PANEL_TYPES.TIME_SERIES, }); const entries = Object.entries( @@ -126,28 +127,13 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => { const initialOperators = getOperatorsBySourceAndPanelType({ dataSource, - panelType, + panelType: panelType || PANEL_TYPES.TIME_SERIES, }); if (JSON.stringify(operators) === JSON.stringify(initialOperators)) return; setOperators(initialOperators); - - const isCurrentOperatorAvailableInList = initialOperators - .map((operator) => operator.value) - .includes(aggregateOperator); - - if (!isCurrentOperatorAvailableInList) { - handleChangeOperator(initialOperators[0].value); - } - }, [ - dataSource, - initialDataSource, - panelType, - operators, - aggregateOperator, - handleChangeOperator, - ]); + }, [dataSource, initialDataSource, panelType, operators]); useEffect(() => { const additionalFilters = getNewListOfAdditionalFilters(dataSource); diff --git a/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts b/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts index 168e5af77a..b3ccc230e9 100644 --- a/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts +++ b/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts @@ -5,11 +5,9 @@ import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { useGetCompositeQueryParam } from './useGetCompositeQueryParam'; import { useQueryBuilder } from './useQueryBuilder'; -type UseShareBuilderUrlParams = { defaultValue: Query }; +export type UseShareBuilderUrlParams = { defaultValue: Query }; -export const useShareBuilderUrl = ({ - defaultValue, -}: UseShareBuilderUrlParams): void => { +export const useShareBuilderUrl = (defaultQuery: Query): void => { const { redirectWithQueryBuilderData, resetStagedQuery } = useQueryBuilder(); const urlQuery = useUrlQuery(); @@ -17,9 +15,9 @@ export const useShareBuilderUrl = ({ useEffect(() => { if (!compositeQuery) { - redirectWithQueryBuilderData(defaultValue); + redirectWithQueryBuilderData(defaultQuery); } - }, [defaultValue, urlQuery, redirectWithQueryBuilderData, compositeQuery]); + }, [defaultQuery, urlQuery, redirectWithQueryBuilderData, compositeQuery]); useEffect( () => (): void => { diff --git a/frontend/src/pages/LogsExplorer/index.tsx b/frontend/src/pages/LogsExplorer/index.tsx index fb8685717c..ef444f4a25 100644 --- a/frontend/src/pages/LogsExplorer/index.tsx +++ b/frontend/src/pages/LogsExplorer/index.tsx @@ -1,21 +1,32 @@ import { Button, Col, Row } from 'antd'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; -import { LogsExplorerChart } from 'container/LogsExplorerChart'; -import { LogsExplorerViews } from 'container/LogsExplorerViews'; +import LogsExplorerChart from 'container/LogsExplorerChart'; +import LogsExplorerViews from 'container/LogsExplorerViews'; import { QueryBuilder } from 'container/QueryBuilder'; import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl'; +import { useMemo } from 'react'; import { DataSource } from 'types/common/queryBuilder'; // ** Styles import { ButtonWrapperStyled, WrapperStyled } from './styles'; function LogsExporer(): JSX.Element { - const { handleRunQuery } = useQueryBuilder(); + const { handleRunQuery, updateAllQueriesOperators } = useQueryBuilder(); const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST); - useShareBuilderUrl({ defaultValue: initialQueriesMap.logs }); + const defaultValue = useMemo( + () => + updateAllQueriesOperators( + initialQueriesMap.logs, + PANEL_TYPES.LIST, + DataSource.LOGS, + ), + [updateAllQueriesOperators], + ); + + useShareBuilderUrl(defaultValue); return ( diff --git a/frontend/src/pages/TracesExplorer/index.tsx b/frontend/src/pages/TracesExplorer/index.tsx index 57c2310d78..39f95bd5e2 100644 --- a/frontend/src/pages/TracesExplorer/index.tsx +++ b/frontend/src/pages/TracesExplorer/index.tsx @@ -37,7 +37,7 @@ function TracesExplorer(): JSX.Element { [redirectWithCurrentTab], ); - useShareBuilderUrl({ defaultValue: initialQueriesMap.traces }); + useShareBuilderUrl(initialQueriesMap.traces); useEffect(() => { if (currentUrlTab) return; diff --git a/frontend/src/providers/QueryBuilder.tsx b/frontend/src/providers/QueryBuilder.tsx index 2389ca01bc..0bfb4c38b9 100644 --- a/frontend/src/providers/QueryBuilder.tsx +++ b/frontend/src/providers/QueryBuilder.tsx @@ -52,11 +52,11 @@ export const QueryBuilderContext = createContext({ stagedQuery: initialQueriesMap.metrics, initialDataSource: null, panelType: PANEL_TYPES.TIME_SERIES, + isEnabledQuery: false, handleSetQueryData: () => {}, handleSetFormulaData: () => {}, handleSetQueryItemData: () => {}, - handleSetPanelType: () => {}, - setupInitialDataSource: () => {}, + handleSetConfig: () => {}, removeQueryBuilderEntityByIndex: () => {}, removeQueryTypeItemByIndex: () => {}, addNewBuilderQuery: () => {}, @@ -65,6 +65,7 @@ export const QueryBuilderContext = createContext({ redirectWithQueryBuilderData: () => {}, handleRunQuery: () => {}, resetStagedQuery: () => {}, + updateAllQueriesOperators: () => initialQueriesMap.metrics, }); export function QueryBuilderProvider({ @@ -75,75 +76,120 @@ export function QueryBuilderProvider({ const location = useLocation(); const compositeQueryParam = useGetCompositeQueryParam(); + const { queryType: queryTypeParam, ...queryState } = + compositeQueryParam || initialQueriesMap.metrics; const [initialDataSource, setInitialDataSource] = useState( null, ); - const [panelType, setPanelType] = useState( - PANEL_TYPES.TIME_SERIES, - ); + const [panelType, setPanelType] = useState(null); const [currentQuery, setCurrentQuery] = useState( - initialQueryState, + queryState || initialQueryState, ); const [stagedQuery, setStagedQuery] = useState(null); - const [queryType, setQueryType] = useState( - EQueryType.QUERY_BUILDER, + const [queryType, setQueryType] = useState(queryTypeParam); + + const getElementWithActualOperator = useCallback( + ( + queryData: IBuilderQuery, + dataSource: DataSource, + currentPanelType: GRAPH_TYPES, + ): IBuilderQuery => { + const initialOperators = getOperatorsBySourceAndPanelType({ + dataSource, + panelType: currentPanelType, + }); + + const isCurrentOperatorAvailableInList = initialOperators + .map((operator) => operator.value) + .includes(queryData.aggregateOperator); + + if (!isCurrentOperatorAvailableInList) { + return { ...queryData, aggregateOperator: initialOperators[0].value }; + } + + return queryData; + }, + [], ); - const initQueryBuilderData = useCallback( - (query: Query): void => { - const { queryType: newQueryType, ...queryState } = query; - + const prepareQueryBuilderData = useCallback( + (query: Query): Query => { const builder: QueryBuilderData = { - queryData: queryState.builder.queryData.map((item) => ({ + queryData: query.builder.queryData.map((item) => ({ ...initialQueryBuilderFormValuesMap[ initialDataSource || DataSource.METRICS ], ...item, })), - queryFormulas: queryState.builder.queryFormulas.map((item) => ({ + queryFormulas: query.builder.queryFormulas.map((item) => ({ ...initialFormulaBuilderFormValues, ...item, })), }; - const promql: IPromQLQuery[] = queryState.promql.map((item) => ({ + const setupedQueryData = builder.queryData.map((item) => { + const currentElement: IBuilderQuery = { + ...item, + groupBy: item.groupBy.map(({ id: _, ...item }) => ({ + ...item, + id: createIdFromObjectFields(item, baseAutoCompleteIdKeysOrder), + })), + aggregateAttribute: { + ...item.aggregateAttribute, + id: createIdFromObjectFields( + item.aggregateAttribute, + baseAutoCompleteIdKeysOrder, + ), + }, + }; + + return currentElement; + }); + + const promql: IPromQLQuery[] = query.promql.map((item) => ({ ...initialQueryPromQLData, ...item, })); - const clickHouse: IClickHouseQuery[] = queryState.clickhouse_sql.map( - (item) => ({ - ...initialClickHouseData, - ...item, - }), - ); - - const type = newQueryType || EQueryType.QUERY_BUILDER; + const clickHouse: IClickHouseQuery[] = query.clickhouse_sql.map((item) => ({ + ...initialClickHouseData, + ...item, + })); const newQueryState: QueryState = { clickhouse_sql: clickHouse, promql, builder: { ...builder, - queryData: builder.queryData.map((q) => ({ - ...q, - groupBy: q.groupBy.map(({ id: _, ...item }) => ({ - ...item, - id: createIdFromObjectFields(item, baseAutoCompleteIdKeysOrder), - })), - aggregateAttribute: { - ...q.aggregateAttribute, - id: createIdFromObjectFields( - q.aggregateAttribute, - baseAutoCompleteIdKeysOrder, - ), - }, - })), + queryData: setupedQueryData, }, + id: query.id, + }; + + const nextQuery: Query = { + ...newQueryState, + queryType: query.queryType, + }; + + return nextQuery; + }, + [initialDataSource], + ); + + const initQueryBuilderData = useCallback( + (query: Query): void => { + const { queryType: newQueryType, ...queryState } = prepareQueryBuilderData( + query, + ); + + const type = newQueryType || EQueryType.QUERY_BUILDER; + + const newQueryState: QueryState = { + ...queryState, id: queryState.id, }; @@ -153,7 +199,19 @@ export function QueryBuilderProvider({ setCurrentQuery(newQueryState); setQueryType(type); }, - [initialDataSource], + [prepareQueryBuilderData], + ); + + const updateAllQueriesOperators = useCallback( + (query: Query, panelType: GRAPH_TYPES, dataSource: DataSource): Query => { + const queryData = query.builder.queryData.map((item) => + getElementWithActualOperator(item, dataSource, panelType), + ); + + return { ...query, builder: { ...query.builder, queryData } }; + }, + + [getElementWithActualOperator], ); const removeQueryBuilderEntityByIndex = useCallback( @@ -161,11 +219,14 @@ export function QueryBuilderProvider({ setCurrentQuery((prevState) => { const currentArray: (IBuilderQuery | IBuilderFormula)[] = prevState.builder[type]; + + const filteredArray = currentArray.filter((_, i) => index !== i); + return { ...prevState, builder: { ...prevState.builder, - [type]: currentArray.filter((_, i) => index !== i), + [type]: filteredArray, }, }; }); @@ -199,20 +260,11 @@ export function QueryBuilderProvider({ existNames, sourceNames: alphabet, }), - ...(initialDataSource - ? { - dataSource: initialDataSource, - aggregateOperator: getOperatorsBySourceAndPanelType({ - dataSource: initialDataSource, - panelType, - })[0].value, - } - : {}), }; return newQuery; }, - [initialDataSource, panelType], + [initialDataSource], ); const createNewBuilderFormula = useCallback((formulas: IBuilderFormula[]) => { @@ -297,12 +349,6 @@ export function QueryBuilderProvider({ }); }, [createNewBuilderFormula]); - const setupInitialDataSource = useCallback( - (newInitialDataSource: DataSource | null) => - setInitialDataSource(newInitialDataSource), - [], - ); - const updateQueryBuilderData: ( arr: T[], index: number, @@ -377,29 +423,33 @@ export function QueryBuilderProvider({ [updateQueryBuilderData], ); - const handleSetPanelType = useCallback((newPanelType: GRAPH_TYPES) => { - setPanelType(newPanelType); - }, []); - const redirectWithQueryBuilderData = useCallback( (query: Partial, searchParams?: Record) => { + const queryType = + !query.queryType || !Object.values(EQueryType).includes(query.queryType) + ? EQueryType.QUERY_BUILDER + : query.queryType; + + const builder = + !query.builder || query.builder.queryData.length === 0 + ? initialQueryState.builder + : query.builder; + + const promql = + !query.promql || query.promql.length === 0 + ? initialQueryState.promql + : query.promql; + + const clickhouseSql = + !query.clickhouse_sql || query.clickhouse_sql.length === 0 + ? initialQueryState.clickhouse_sql + : query.clickhouse_sql; + const currentGeneratedQuery: Query = { - queryType: - !query.queryType || !Object.values(EQueryType).includes(query.queryType) - ? EQueryType.QUERY_BUILDER - : query.queryType, - builder: - !query.builder || query.builder.queryData.length === 0 - ? initialQueryState.builder - : query.builder, - promql: - !query.promql || query.promql.length === 0 - ? initialQueryState.promql - : query.promql, - clickhouse_sql: - !query.clickhouse_sql || query.clickhouse_sql.length === 0 - ? initialQueryState.clickhouse_sql - : query.clickhouse_sql, + queryType, + builder, + promql, + clickhouse_sql: clickhouseSql, id: uuid(), }; @@ -418,6 +468,14 @@ export function QueryBuilderProvider({ [history, location, urlQuery], ); + const handleSetConfig = useCallback( + (newPanelType: GRAPH_TYPES, dataSource: DataSource | null) => { + setPanelType(newPanelType); + setInitialDataSource(dataSource); + }, + [], + ); + const handleRunQuery = useCallback(() => { redirectWithQueryBuilderData({ ...currentQuery, queryType }); }, [redirectWithQueryBuilderData, currentQuery, queryType]); @@ -458,17 +516,22 @@ export function QueryBuilderProvider({ [currentQuery, queryType], ); + const isEnabledQuery = useMemo(() => !!stagedQuery && !!panelType, [ + stagedQuery, + panelType, + ]); + const contextValues: QueryBuilderContextType = useMemo( () => ({ currentQuery: query, stagedQuery, initialDataSource, panelType, + isEnabledQuery, handleSetQueryData, handleSetFormulaData, handleSetQueryItemData, - handleSetPanelType, - setupInitialDataSource, + handleSetConfig, removeQueryBuilderEntityByIndex, removeQueryTypeItemByIndex, addNewBuilderQuery, @@ -477,17 +540,18 @@ export function QueryBuilderProvider({ redirectWithQueryBuilderData, handleRunQuery, resetStagedQuery, + updateAllQueriesOperators, }), [ query, stagedQuery, initialDataSource, panelType, + isEnabledQuery, handleSetQueryData, handleSetFormulaData, handleSetQueryItemData, - handleSetPanelType, - setupInitialDataSource, + handleSetConfig, removeQueryBuilderEntityByIndex, removeQueryTypeItemByIndex, addNewBuilderQuery, @@ -496,6 +560,7 @@ export function QueryBuilderProvider({ redirectWithQueryBuilderData, handleRunQuery, resetStagedQuery, + updateAllQueriesOperators, ], ); diff --git a/frontend/src/types/api/widgets/getQuery.ts b/frontend/src/types/api/widgets/getQuery.ts index 7452b52dda..f97ffb59e2 100644 --- a/frontend/src/types/api/widgets/getQuery.ts +++ b/frontend/src/types/api/widgets/getQuery.ts @@ -1,8 +1,12 @@ +import { ILog } from '../logs/log'; + export interface PayloadProps { status: 'success' | 'error'; result: QueryData[]; } +type ListItem = { timestamp: string; data: Omit }; + export interface QueryData { metric: { [key: string]: string; @@ -20,7 +24,7 @@ export interface SeriesItem { } export interface QueryDataV3 { - list: null; + list: ListItem[] | null; queryName: string; legend?: string; series: SeriesItem[] | null; diff --git a/frontend/src/types/common/queryBuilder.ts b/frontend/src/types/common/queryBuilder.ts index 16b6f8cd08..8681468a6f 100644 --- a/frontend/src/types/common/queryBuilder.ts +++ b/frontend/src/types/common/queryBuilder.ts @@ -156,7 +156,8 @@ export type QueryBuilderContextType = { currentQuery: Query; stagedQuery: Query | null; initialDataSource: DataSource | null; - panelType: GRAPH_TYPES; + panelType: GRAPH_TYPES | null; + isEnabledQuery: boolean; handleSetQueryData: (index: number, queryData: IBuilderQuery) => void; handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void; handleSetQueryItemData: ( @@ -164,8 +165,10 @@ export type QueryBuilderContextType = { type: EQueryType.PROM | EQueryType.CLICKHOUSE, newQueryData: IPromQLQuery | IClickHouseQuery, ) => void; - handleSetPanelType: (newPanelType: GRAPH_TYPES) => void; - setupInitialDataSource: (newInitialDataSource: DataSource | null) => void; + handleSetConfig: ( + newPanelType: GRAPH_TYPES, + dataSource: DataSource | null, + ) => void; removeQueryBuilderEntityByIndex: ( type: keyof QueryBuilderData, index: number, @@ -183,6 +186,11 @@ export type QueryBuilderContextType = { ) => void; handleRunQuery: () => void; resetStagedQuery: () => void; + updateAllQueriesOperators: ( + queryData: Query, + panelType: GRAPH_TYPES, + dataSource: DataSource, + ) => Query; }; export type QueryAdditionalFilter = {