From 50270281e3d18c3c440f9fb0663e2a8372f23664 Mon Sep 17 00:00:00 2001 From: palashgdev Date: Wed, 1 Mar 2023 17:18:02 +0530 Subject: [PATCH] fix: Logs Live Tail is fixed (#2380) * logs is updated * fix: log live tail is updated * fix: live tail is fixed * chore: build is fixed * chore: useEffect is removed * chore: getLogsAggregate callback is added in the useEffect --- frontend/src/container/LogLiveTail/index.tsx | 120 ++++++++++++++---- .../src/container/LogsAggregate/index.tsx | 89 ++++--------- .../src/container/LogsSearchFilter/index.tsx | 20 ++- frontend/src/hooks/useInterval.ts | 21 ++- 4 files changed, 155 insertions(+), 95 deletions(-) diff --git a/frontend/src/container/LogLiveTail/index.tsx b/frontend/src/container/LogLiveTail/index.tsx index f0cacd527c..1139342a9d 100644 --- a/frontend/src/container/LogLiveTail/index.tsx +++ b/frontend/src/container/LogLiveTail/index.tsx @@ -8,40 +8,50 @@ import { Button, Popover, Select, Space } from 'antd'; import { LiveTail } from 'api/logs/livetail'; import dayjs from 'dayjs'; import { useIsDarkMode } from 'hooks/useDarkMode'; +import { useNotifications } from 'hooks/useNotifications'; +import getStep from 'lib/getStep'; import { throttle } from 'lodash-es'; import React, { useCallback, useEffect, useMemo, useRef } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { connect, useDispatch, useSelector } from 'react-redux'; +import { bindActionCreators, Dispatch } from 'redux'; +import { ThunkDispatch } from 'redux-thunk'; +import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate'; import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; import { UPDATE_AUTO_REFRESH_DISABLED } from 'types/actions/globalTime'; import { FLUSH_LOGS, PUSH_LIVE_TAIL_EVENT, SET_LIVE_TAIL_START_TIME, + SET_LOADING, TOGGLE_LIVE_TAIL, } from 'types/actions/logs'; import { TLogsLiveTailState } from 'types/api/logs/liveTail'; +import { ILog } from 'types/api/logs/log'; import { GlobalReducer } from 'types/reducer/globalTime'; import { ILogsReducer } from 'types/reducer/logs'; import { TIME_PICKER_OPTIONS } from './config'; import { StopContainer, TimePickerCard, TimePickerSelect } from './styles'; -const { Option } = Select; - -function LogLiveTail(): JSX.Element { +function LogLiveTail({ getLogsAggregate }: Props): JSX.Element { const { liveTail, searchFilter: { queryString }, liveTailStartRange, logs, + idEnd, + idStart, } = useSelector((state) => state.logs); + const isDarkMode = useIsDarkMode(); const { selectedAutoRefreshInterval } = useSelector( (state) => state.globalTime, ); + const { notifications } = useNotifications(); - const dispatch = useDispatch(); + const dispatch = useDispatch>(); const handleLiveTail = (toggleState: TLogsLiveTailState): void => { dispatch({ type: TOGGLE_LIVE_TAIL, @@ -53,7 +63,7 @@ function LogLiveTail(): JSX.Element { }); }; - const batchedEventsRef = useRef[]>([]); + const batchedEventsRef = useRef([]); const pushLiveLog = useCallback(() => { dispatch({ @@ -75,47 +85,76 @@ function LogLiveTail(): JSX.Element { [pushLiveLogThrottled], ); + const firstLogsId = useMemo(() => logs[0]?.id, [logs]); + // This ref depicts thats whether the live tail is played from paused state or not. - const liveTailSourceRef = useRef(null); + const liveTailSourceRef = useRef(); + useEffect(() => { if (liveTail === 'PLAYING') { - // console.log('Starting Live Tail', logs.length); const timeStamp = dayjs().subtract(liveTailStartRange, 'minute').valueOf(); const queryParams = new URLSearchParams({ ...(queryString ? { q: queryString } : {}), timestampStart: (timeStamp * 1e6) as never, - ...(liveTailSourceRef.current && logs.length > 0 + ...(liveTailSourceRef.current && firstLogsId ? { - idGt: logs[0].id, + idGt: firstLogsId, } : {}), }); + + if (liveTailSourceRef.current) { + liveTailSourceRef.current.close(); + } + const source = LiveTail(queryParams.toString()); liveTailSourceRef.current = source; source.onmessage = function connectionMessage(e): void { batchLiveLog(e); }; - // source.onopen = function connectionOpen(): void { }; source.onerror = function connectionError(event: unknown): void { console.error(event); source.close(); dispatch({ type: TOGGLE_LIVE_TAIL, + payload: 'STOPPED', + }); + dispatch({ + type: SET_LOADING, payload: false, }); + notifications.error({ + message: 'Live tail stopped due to some error.', + }); }; - } else if (liveTailSourceRef.current && liveTailSourceRef.current.close) { - liveTailSourceRef.current?.close(); } if (liveTail === 'STOPPED') { - liveTailSourceRef.current = null; + liveTailSourceRef.current = undefined; } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [liveTail]); + }, [liveTail, queryString, notifications, dispatch]); const handleLiveTailStart = (): void => { handleLiveTail('PLAYING'); + const startTime = + dayjs().subtract(liveTailStartRange, 'minute').valueOf() * 1e6; + + const endTime = dayjs().valueOf() * 1e6; + + getLogsAggregate({ + timestampStart: startTime, + timestampEnd: endTime, + step: getStep({ + start: startTime, + end: endTime, + inputFormat: 'ns', + }), + q: queryString, + ...(idStart ? { idGt: idStart } : {}), + ...(idEnd ? { idLt: idEnd } : {}), + }); + if (!liveTailSourceRef.current) { dispatch({ type: FLUSH_LOGS, @@ -129,16 +168,18 @@ function LogLiveTail(): JSX.Element { disabled={liveTail === 'PLAYING'} value={liveTailStartRange} onChange={(value): void => { - dispatch({ - type: SET_LIVE_TAIL_START_TIME, - payload: value, - }); + if (typeof value === 'number') { + dispatch({ + type: SET_LIVE_TAIL_START_TIME, + payload: value, + }); + } }} > {TIME_PICKER_OPTIONS.map((optionData) => ( - + ))} ), @@ -149,13 +190,28 @@ function LogLiveTail(): JSX.Element { selectedAutoRefreshInterval, ]); + const onLiveTailStop = (): void => { + handleLiveTail('STOPPED'); + dispatch({ + type: UPDATE_AUTO_REFRESH_DISABLED, + payload: false, + }); + dispatch({ + type: SET_LOADING, + payload: false, + }); + if (liveTailSourceRef.current) { + liveTailSourceRef.current.close(); + } + }; + return ( {liveTail === 'PLAYING' ? ( )} @@ -196,4 +248,16 @@ function LogLiveTail(): JSX.Element { ); } -export default LogLiveTail; +interface DispatchProps { + getLogsAggregate: typeof getLogsAggregate; +} + +type Props = DispatchProps; + +const mapDispatchToProps = ( + dispatch: ThunkDispatch, +): DispatchProps => ({ + getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch), +}); + +export default connect(null, mapDispatchToProps)(LogLiveTail); diff --git a/frontend/src/container/LogsAggregate/index.tsx b/frontend/src/container/LogsAggregate/index.tsx index 5084f2fdca..c1217c0e46 100644 --- a/frontend/src/container/LogsAggregate/index.tsx +++ b/frontend/src/container/LogsAggregate/index.tsx @@ -2,20 +2,20 @@ import { blue } from '@ant-design/colors'; import Graph from 'components/Graph'; import Spinner from 'components/Spinner'; import dayjs from 'dayjs'; +import useInterval from 'hooks/useInterval'; import getStep from 'lib/getStep'; -import React, { memo, useEffect, useMemo, useRef } from 'react'; +import React, { useMemo } from 'react'; import { connect, useSelector } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; +import { bindActionCreators } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate'; import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; -import { GlobalReducer } from 'types/reducer/globalTime'; import { ILogsReducer } from 'types/reducer/logs'; import { Container } from './styles'; -function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element { +function LogsAggregate({ getLogsAggregate }: DispatchProps): JSX.Element { const { searchFilter: { queryString }, idEnd, @@ -26,57 +26,30 @@ function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element { liveTailStartRange, } = useSelector((state) => state.logs); - const { maxTime, minTime } = useSelector( - (state) => state.globalTime, + useInterval( + () => { + const startTime = + dayjs().subtract(liveTailStartRange, 'minute').valueOf() * 1e6; + + const endTime = dayjs().valueOf() * 1e6; + + getLogsAggregate({ + timestampStart: startTime, + timestampEnd: endTime, + step: getStep({ + start: startTime, + end: endTime, + inputFormat: 'ns', + }), + q: queryString, + ...(idStart ? { idGt: idStart } : {}), + ...(idEnd ? { idLt: idEnd } : {}), + }); + }, + 60000, + liveTail === 'PLAYING', ); - const reFetchIntervalRef = useRef | null>(null); - - useEffect(() => { - switch (liveTail) { - case 'STOPPED': { - if (reFetchIntervalRef.current) { - clearInterval(reFetchIntervalRef.current); - } - reFetchIntervalRef.current = null; - break; - } - - case 'PLAYING': { - const aggregateCall = (): void => { - const startTime = - dayjs().subtract(liveTailStartRange, 'minute').valueOf() * 1e6; - const endTime = dayjs().valueOf() * 1e6; - getLogsAggregate({ - timestampStart: startTime, - timestampEnd: endTime, - step: getStep({ - start: startTime, - end: endTime, - inputFormat: 'ns', - }), - q: queryString, - ...(idStart ? { idGt: idStart } : {}), - ...(idEnd ? { idLt: idEnd } : {}), - }); - }; - aggregateCall(); - reFetchIntervalRef.current = setInterval(aggregateCall, 60000); - break; - } - case 'PAUSED': { - if (reFetchIntervalRef.current) { - clearInterval(reFetchIntervalRef.current); - } - break; - } - default: { - break; - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [getLogsAggregate, maxTime, minTime, liveTail]); - const graphData = useMemo( () => ({ labels: logsAggregate.map((s) => new Date(s.timestamp / 1000000)), @@ -107,14 +80,8 @@ function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element { ); } -interface LogsAggregateProps { - getLogsAggregate: (arg0: Parameters[0]) => void; -} - interface DispatchProps { - getLogsAggregate: ( - props: Parameters[0], - ) => (dispatch: Dispatch) => void; + getLogsAggregate: typeof getLogsAggregate; } const mapDispatchToProps = ( @@ -123,4 +90,4 @@ const mapDispatchToProps = ( getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch), }); -export default connect(null, mapDispatchToProps)(memo(LogsAggregate)); +export default connect(null, mapDispatchToProps)(LogsAggregate); diff --git a/frontend/src/container/LogsSearchFilter/index.tsx b/frontend/src/container/LogsSearchFilter/index.tsx index 3751b91487..670c58cf70 100644 --- a/frontend/src/container/LogsSearchFilter/index.tsx +++ b/frontend/src/container/LogsSearchFilter/index.tsx @@ -74,6 +74,7 @@ function SearchFilter({ const handleSearch = useCallback( (customQuery: string) => { getLogsFields(); + const { maxTime, minTime } = globalTime; if (liveTail === 'PLAYING') { dispatch({ @@ -87,9 +88,24 @@ function SearchFilter({ type: TOGGLE_LIVE_TAIL, payload: liveTail, }); - } else { - const { maxTime, minTime } = globalTime; + dispatch({ + type: SET_LOADING, + payload: false, + }); + getLogsAggregate({ + timestampStart: minTime, + timestampEnd: maxTime, + step: getStep({ + start: minTime, + end: maxTime, + inputFormat: 'ns', + }), + q: customQuery, + ...(idStart ? { idGt: idStart } : {}), + ...(idEnd ? { idLt: idEnd } : {}), + }); + } else { getLogs({ q: customQuery, limit: logLinesPerPage, diff --git a/frontend/src/hooks/useInterval.ts b/frontend/src/hooks/useInterval.ts index 09438509dd..2b332b9041 100644 --- a/frontend/src/hooks/useInterval.ts +++ b/frontend/src/hooks/useInterval.ts @@ -1,6 +1,10 @@ import { useEffect, useRef } from 'react'; -function useInterval(callback: () => void, delay: number): void { +function useInterval( + callback: () => void, + delay: number, + enabled = true, +): void { const savedCallback = useRef<() => void>(); useEffect(() => { @@ -14,9 +18,18 @@ function useInterval(callback: () => void, delay: number): void { } } - const id = setInterval(tick, delay); - return (): void => clearInterval(id); - }, [delay]); + let id: NodeJS.Timer; + + if (enabled) { + id = setInterval(tick, delay); + } + + return (): void => { + if (id) { + clearInterval(id); + } + }; + }, [delay, enabled]); } export default useInterval;