feat: use prefetched query in live (#3450)

* feat: create live logs page and custom top nav

* feat: add live logs where clause

* fix: success button color

* fix: turn back color

* fix: undefined scenario

* feat: get live data

* fix: change color, change number format

* feat: add live logs list

* feat: hide view if error, clear logs

* feat: add condition for disable initial loading

* fix: double request

* fix: render id in the where clause

* fix: render where clause and live list

* fix: last log padding

* fix: list data loading

* fix: no logs text

* fix: logs list size

* fix: small issues

* feat: use prefetched query in live

* chore: useMemo is updated

* feat: add live logs list (#3341)

* feat: add live logs list

* feat: hide view if error, clear logs

* feat: add condition for disable initial loading

* fix: double request

* fix: render id in the where clause

* fix: render where clause and live list

* fix: last log padding

* fix: list data loading

* fix: no logs text

* fix: logs list size

* fix: small issues

* fix: render view with memo

---------

Co-authored-by: Palash Gupta <palashgdev@gmail.com>

* chore: alignment is updated

* fix: action column size

---------

Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
Yevhen Shevchenko 2023-08-29 17:09:40 +03:00 committed by GitHub
parent d184486978
commit 7e297dcb75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 207 additions and 87 deletions

View File

@ -1,29 +1,49 @@
import { ArrowLeftOutlined } from '@ant-design/icons';
import { Button } from 'antd';
import { initialQueriesMap } from 'constants/queryBuilder';
import {
initialQueryBuilderFormValuesMap,
PANEL_TYPES,
} from 'constants/queryBuilder';
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import ROUTES from 'constants/routes';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import { DataSource } from 'types/common/queryBuilder';
import { constructCompositeQuery } from '../constants';
function BackButton(): JSX.Element {
const history = useHistory();
const { resetQuery } = useQueryBuilder();
const { updateAllQueriesOperators, resetQuery } = useQueryBuilder();
const compositeQuery = useGetCompositeQueryParam();
const handleBack = useCallback(() => {
const compositeQuery = initialQueriesMap.logs;
if (!compositeQuery) return;
const JSONCompositeQuery = encodeURIComponent(JSON.stringify(compositeQuery));
const nextCompositeQuery = constructCompositeQuery({
query: compositeQuery,
initialQueryData: initialQueryBuilderFormValuesMap.logs,
customQueryData: { disabled: false },
});
const path = `${ROUTES.LOGS_EXPLORER}?${JSONCompositeQuery}`;
const updatedQuery = updateAllQueriesOperators(
nextCompositeQuery,
PANEL_TYPES.LIST,
DataSource.LOGS,
);
const { queryType, ...queryState } = initialQueriesMap.logs;
resetQuery(updatedQuery);
resetQuery(queryState);
const JSONCompositeQuery = encodeURIComponent(JSON.stringify(updatedQuery));
const path = `${ROUTES.LOGS_EXPLORER}?${queryParamNamesMap.compositeQuery}=${JSONCompositeQuery}`;
history.push(path);
}, [history, resetQuery]);
}, [history, compositeQuery, resetQuery, updateAllQueriesOperators]);
return (
<Button icon={<ArrowLeftOutlined />} onClick={handleBack}>

View File

@ -14,6 +14,7 @@ import { useNotifications } from 'hooks/useNotifications';
import { useEventSource } from 'providers/EventSource';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { prepareQueryRangePayload } from 'store/actions/dashboard/prepareQueryRangePayload';
import { AppState } from 'store/reducers';
import { ILog } from 'types/api/logs/log';
@ -23,14 +24,18 @@ import { GlobalReducer } from 'types/reducer/globalTime';
import { idObject } from '../constants';
import ListViewPanel from '../ListViewPanel';
import LiveLogsList from '../LiveLogsList';
import { QueryHistoryState } from '../types';
import { prepareQueryByFilter } from '../utils';
import { ContentWrapper, LiveLogsChart, Wrapper } from './styles';
function LiveLogsContainer(): JSX.Element {
const location = useLocation();
const [logs, setLogs] = useState<ILog[]>([]);
const { stagedQuery } = useQueryBuilder();
const queryLocationState = location.state as QueryHistoryState;
const batchedEventsRef = useRef<ILog[]>([]);
const { notifications } = useNotifications();
@ -44,20 +49,23 @@ function LiveLogsContainer(): JSX.Element {
handleStartOpenConnection,
handleCloseConnection,
initialLoading,
isConnectionLoading,
} = useEventSource();
const compositeQuery = useGetCompositeQueryParam();
const updateLogs = useCallback(() => {
const reversedData = batchedEventsRef.current.reverse();
const updateLogs = useCallback((newLogs: ILog[]) => {
setLogs((prevState) =>
[...reversedData, ...prevState].slice(0, MAX_LOGS_LIST_SIZE),
[...newLogs, ...prevState].slice(0, MAX_LOGS_LIST_SIZE),
);
batchedEventsRef.current = [];
}, []);
const debouncedUpdateLogs = useDebouncedFn(updateLogs, 500);
const debouncedUpdateLogs = useDebouncedFn(() => {
const reversedData = batchedEventsRef.current.reverse();
updateLogs(reversedData);
}, 500);
const batchLiveLog = useCallback(
(log: ILog): void => {
@ -99,40 +107,66 @@ function LiveLogsContainer(): JSX.Element {
[logs],
);
const handleStartNewConnection = useCallback(() => {
const openConnection = useCallback(
(query: Query) => {
const { queryPayload } = prepareQueryRangePayload({
query,
graphType: PANEL_TYPES.LIST,
selectedTime: 'GLOBAL_TIME',
globalSelectedInterval: globalSelectedTime,
});
const encodedQueryPayload = encodeURIComponent(JSON.stringify(queryPayload));
const queryString = `q=${encodedQueryPayload}`;
handleStartOpenConnection({ queryString });
},
[globalSelectedTime, handleStartOpenConnection],
);
const handleStartNewConnection = useCallback(
(query: Query) => {
handleCloseConnection();
const preparedQuery = getPreparedQuery(query);
openConnection(preparedQuery);
},
[getPreparedQuery, handleCloseConnection, openConnection],
);
useEffect(() => {
if (!compositeQuery) return;
handleCloseConnection();
const preparedQuery = getPreparedQuery(compositeQuery);
const { queryPayload } = prepareQueryRangePayload({
query: preparedQuery,
graphType: PANEL_TYPES.LIST,
selectedTime: 'GLOBAL_TIME',
globalSelectedInterval: globalSelectedTime,
});
const encodedQueryPayload = encodeURIComponent(JSON.stringify(queryPayload));
const queryString = `q=${encodedQueryPayload}`;
handleStartOpenConnection({ queryString });
if (
(initialLoading && !isConnectionLoading) ||
compositeQuery.id !== stagedQuery?.id
) {
handleStartNewConnection(compositeQuery);
}
}, [
compositeQuery,
globalSelectedTime,
getPreparedQuery,
handleCloseConnection,
handleStartOpenConnection,
initialLoading,
stagedQuery,
isConnectionLoading,
openConnection,
handleStartNewConnection,
]);
useEffect(() => {
if (!compositeQuery || !stagedQuery) return;
const prefetchedList = queryLocationState?.listQueryPayload[0]?.list;
if (compositeQuery.id !== stagedQuery.id || initialLoading) {
handleStartNewConnection();
if (prefetchedList) {
const prefetchedLogs: ILog[] = prefetchedList
.map((item) => ({
...item.data,
timestamp: item.timestamp,
}))
.reverse();
updateLogs(prefetchedLogs);
}
}, [stagedQuery, initialLoading, compositeQuery, handleStartNewConnection]);
}, [queryLocationState, updateLogs]);
return (
<Wrapper>
@ -141,14 +175,16 @@ function LiveLogsContainer(): JSX.Element {
<Col span={24}>
<FiltersInput />
</Col>
{initialLoading ? (
{initialLoading && logs.length === 0 ? (
<Col span={24}>
<Spinner style={{ height: 'auto' }} tip="Fetching Logs" />
</Col>
) : (
<>
<Col span={24}>
<LiveLogsChart />
<LiveLogsChart
initialData={queryLocationState?.graphQueryPayload || null}
/>
</Col>
<Col span={24}>
<ListViewPanel />

View File

@ -27,7 +27,7 @@ function LiveLogsList({ logs }: LiveLogsListProps): JSX.Element {
const { t } = useTranslation(['logs']);
const { isConnectionError, isConnectionLoading } = useEventSource();
const { isConnectionLoading } = useEventSource();
const { activeLogId } = useCopyLogLink();
@ -98,7 +98,7 @@ function LiveLogsList({ logs }: LiveLogsListProps): JSX.Element {
{logs.length === 0 && <Typography>{t('fetching_log_lines')}</Typography>}
{!isConnectionError && logs.length !== 0 && (
{logs.length !== 0 && (
<InfinityWrapperStyled>
{options.format === 'table' ? (
<InfinityTableView

View File

@ -11,9 +11,12 @@ import { DataSource, LogsAggregatorOperator } from 'types/common/queryBuilder';
import { LiveLogsListChartProps } from './types';
function LiveLogsListChart({ className }: LiveLogsListChartProps): JSX.Element {
function LiveLogsListChart({
className,
initialData,
}: LiveLogsListChartProps): JSX.Element {
const { stagedQuery } = useQueryBuilder();
const { isConnectionOpen, isConnectionLoading } = useEventSource();
const { isConnectionOpen } = useEventSource();
const listChartQuery: Query | null = useMemo(() => {
if (!stagedQuery) return null;
@ -47,19 +50,16 @@ function LiveLogsListChart({ className }: LiveLogsListChartProps): JSX.Element {
);
const chartData: QueryData[] = useMemo(() => {
if (initialData) return initialData;
if (!data) return [];
return data.payload.data.result;
}, [data]);
const isLoading = useMemo(
() => isFetching || (isConnectionLoading && !isConnectionOpen),
[isConnectionLoading, isConnectionOpen, isFetching],
);
}, [data, initialData]);
return (
<LogsExplorerChart
isLoading={isLoading}
isLoading={initialData ? false : isFetching}
data={chartData}
isLabelEnabled={false}
className={className}

View File

@ -1,3 +1,6 @@
import { QueryData } from 'types/api/widgets/getQuery';
export type LiveLogsListChartProps = {
className?: string;
initialData: QueryData[] | null;
};

View File

@ -4,25 +4,44 @@ import {
} from 'constants/queryBuilder';
import { FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
import { LogsAggregatorOperator } from 'types/common/queryBuilder';
export const liveLogsCompositeQuery: Query = {
...initialQueriesMap.logs,
builder: {
...initialQueriesMap.logs.builder,
queryData: [
{
...initialQueryBuilderFormValuesMap.logs,
aggregateOperator: LogsAggregatorOperator.NOOP,
disabled: true,
pageSize: 10,
orderBy: [{ columnName: 'timestamp', order: FILTERS.DESC }],
},
],
},
export const defaultLiveQueryDataConfig: Partial<IBuilderQuery> = {
aggregateOperator: LogsAggregatorOperator.NOOP,
disabled: true,
pageSize: 10,
orderBy: [{ columnName: 'timestamp', order: FILTERS.DESC }],
};
type GetDefaultCompositeQueryParams = {
query: Query;
initialQueryData: IBuilderQuery;
customQueryData?: Partial<IBuilderQuery>;
};
export const constructCompositeQuery = ({
query,
initialQueryData,
customQueryData,
}: GetDefaultCompositeQueryParams): Query => ({
...query,
builder: {
...query.builder,
queryData: query.builder.queryData.map((item) => ({
...initialQueryData,
...item,
...customQueryData,
})),
},
});
export const liveLogsCompositeQuery = constructCompositeQuery({
query: initialQueriesMap.logs,
initialQueryData: initialQueryBuilderFormValuesMap.logs,
customQueryData: defaultLiveQueryDataConfig,
});
export const idObject: BaseAutocompleteData = {
key: 'id',
type: '',

View File

@ -0,0 +1,6 @@
import { QueryData, QueryDataV3 } from 'types/api/widgets/getQuery';
export type QueryHistoryState = {
graphQueryPayload: QueryData[];
listQueryPayload: QueryDataV3[];
};

View File

@ -1,5 +0,0 @@
import { Query } from 'types/api/queryBuilder/queryBuilderData';
export type LiveLogsTopNavProps = {
getPreparedQuery: (query: Query) => Query;
};

View File

@ -17,7 +17,7 @@ function LocalTopNav({
<Col span={8}>
<Row justify="end">
<Space align="center" size={30} direction="horizontal">
<Space align="start" size={30} direction="horizontal">
{actions}
{renderPermissions?.isDateTimeEnabled && (
<div>

View File

@ -91,7 +91,7 @@ function TableView({
const columns: ColumnsType<DataType> = [
{
title: 'Action',
width: 15,
width: 30,
render: (fieldData: Record<string, string>): JSX.Element | null => {
const fieldKey = fieldData.field.split('.').slice(-1);
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {

View File

@ -1,24 +1,72 @@
import { PlayCircleFilled } from '@ant-design/icons';
import {
initialQueryBuilderFormValuesMap,
PANEL_TYPES,
} from 'constants/queryBuilder';
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import ROUTES from 'constants/routes';
import { liveLogsCompositeQuery } from 'container/LiveLogs/constants';
import {
constructCompositeQuery,
defaultLiveQueryDataConfig,
} from 'container/LiveLogs/constants';
import { QueryHistoryState } from 'container/LiveLogs/types';
import LocalTopNav from 'container/LocalTopNav';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useCallback, useMemo } from 'react';
import { useQueryClient } from 'react-query';
import { useHistory } from 'react-router-dom';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { LiveButtonStyled } from './styles';
function LogsTopNav(): JSX.Element {
const history = useHistory();
const queryClient = useQueryClient();
const { stagedQuery, panelType } = useQueryBuilder();
const handleGoLive = useCallback(() => {
const JSONCompositeQuery = encodeURIComponent(
JSON.stringify(liveLogsCompositeQuery),
);
if (!stagedQuery) return;
const path = `${ROUTES.LIVE_LOGS}?${JSONCompositeQuery}`;
let queryHistoryState: QueryHistoryState | null = null;
history.push(path);
}, [history]);
const compositeQuery = constructCompositeQuery({
query: stagedQuery,
initialQueryData: initialQueryBuilderFormValuesMap.logs,
customQueryData: defaultLiveQueryDataConfig,
});
const isListView =
panelType === PANEL_TYPES.LIST && stagedQuery.builder.queryData[0];
if (isListView) {
const [graphQuery, listQuery] = queryClient.getQueriesData<
SuccessResponse<MetricRangePayloadProps> | ErrorResponse
>({
queryKey: REACT_QUERY_KEY.GET_QUERY_RANGE,
active: true,
});
queryHistoryState = {
graphQueryPayload:
graphQuery && graphQuery[1]
? graphQuery[1].payload?.data.result || []
: [],
listQueryPayload:
listQuery && listQuery[1]
? listQuery[1].payload?.data.newResult.data.result || []
: [],
};
}
const JSONCompositeQuery = encodeURIComponent(JSON.stringify(compositeQuery));
const path = `${ROUTES.LIVE_LOGS}?${queryParamNamesMap.compositeQuery}=${JSONCompositeQuery}`;
history.push(path, queryHistoryState);
}, [history, panelType, queryClient, stagedQuery]);
const liveButton = useMemo(
() => (

View File

@ -8,7 +8,7 @@ import { useQueryBuilder } from './useQueryBuilder';
export type UseShareBuilderUrlParams = { defaultValue: Query };
export const useShareBuilderUrl = (defaultQuery: Query): void => {
const { redirectWithQueryBuilderData, resetQuery } = useQueryBuilder();
const { redirectWithQueryBuilderData } = useQueryBuilder();
const urlQuery = useUrlQuery();
const compositeQuery = useGetCompositeQueryParam();
@ -18,11 +18,4 @@ export const useShareBuilderUrl = (defaultQuery: Query): void => {
redirectWithQueryBuilderData(defaultQuery);
}
}, [defaultQuery, urlQuery, redirectWithQueryBuilderData, compositeQuery]);
useEffect(
() => (): void => {
resetQuery();
},
[resetQuery],
);
};