mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-18 04:25:57 +08:00
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:
parent
d184486978
commit
7e297dcb75
@ -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}>
|
||||
|
@ -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 />
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -1,3 +1,6 @@
|
||||
import { QueryData } from 'types/api/widgets/getQuery';
|
||||
|
||||
export type LiveLogsListChartProps = {
|
||||
className?: string;
|
||||
initialData: QueryData[] | null;
|
||||
};
|
||||
|
@ -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: '',
|
||||
|
6
frontend/src/container/LiveLogs/types.ts
Normal file
6
frontend/src/container/LiveLogs/types.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { QueryData, QueryDataV3 } from 'types/api/widgets/getQuery';
|
||||
|
||||
export type QueryHistoryState = {
|
||||
graphQueryPayload: QueryData[];
|
||||
listQueryPayload: QueryDataV3[];
|
||||
};
|
@ -1,5 +0,0 @@
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export type LiveLogsTopNavProps = {
|
||||
getPreparedQuery: (query: Query) => Query;
|
||||
};
|
@ -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>
|
||||
|
@ -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])) {
|
||||
|
@ -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(
|
||||
() => (
|
||||
|
@ -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],
|
||||
);
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user