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
This commit is contained in:
palashgdev 2023-03-01 17:18:02 +05:30 committed by GitHub
parent 5e5e81d81d
commit 50270281e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 155 additions and 95 deletions

View File

@ -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<AppState, ILogsReducer>((state) => state.logs);
const isDarkMode = useIsDarkMode();
const { selectedAutoRefreshInterval } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const { notifications } = useNotifications();
const dispatch = useDispatch();
const dispatch = useDispatch<Dispatch<AppActions>>();
const handleLiveTail = (toggleState: TLogsLiveTailState): void => {
dispatch({
type: TOGGLE_LIVE_TAIL,
@ -53,7 +63,7 @@ function LogLiveTail(): JSX.Element {
});
};
const batchedEventsRef = useRef<Record<string, unknown>[]>([]);
const batchedEventsRef = useRef<ILog[]>([]);
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<EventSource | null>(null);
const liveTailSourceRef = useRef<EventSource>();
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) => (
<Option key={optionData.label} value={optionData.value}>
<Select.Option key={optionData.label} value={optionData.value}>
Last {optionData.label}
</Option>
</Select.Option>
))}
</TimePickerSelect>
),
@ -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 (
<TimePickerCard>
<Space size={0} align="center">
{liveTail === 'PLAYING' ? (
<Button
type="primary"
onClick={(): void => handleLiveTail('PAUSED')}
onClick={onLiveTailStop}
title="Pause live tail"
style={{ background: green[6] }}
>
@ -174,11 +230,7 @@ function LogLiveTail(): JSX.Element {
)}
{liveTail !== 'STOPPED' && (
<Button
type="dashed"
onClick={(): void => handleLiveTail('STOPPED')}
title="Exit live tail"
>
<Button type="dashed" onClick={onLiveTailStop} title="Exit live tail">
<StopContainer isDarkMode={isDarkMode} />
</Button>
)}
@ -196,4 +248,16 @@ function LogLiveTail(): JSX.Element {
);
}
export default LogLiveTail;
interface DispatchProps {
getLogsAggregate: typeof getLogsAggregate;
}
type Props = DispatchProps;
const mapDispatchToProps = (
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
): DispatchProps => ({
getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch),
});
export default connect(null, mapDispatchToProps)(LogLiveTail);

View File

@ -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<AppState, ILogsReducer>((state) => state.logs);
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(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<ReturnType<typeof setInterval> | 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<typeof getLogsAggregate>[0]) => void;
}
interface DispatchProps {
getLogsAggregate: (
props: Parameters<typeof getLogsAggregate>[0],
) => (dispatch: Dispatch<AppActions>) => 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);

View File

@ -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,

View File

@ -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;