mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 20:05:58 +08:00
fix: Logs issues are fixed (#1727)
* feat: logs is updated * chore: width:100% is removed * chore: position of filter is updated * chore: min time and max time are now tracked from global state Co-authored-by: Pranay Prateek <pranay@signoz.io>
This commit is contained in:
parent
1273bb5865
commit
4c0d573760
@ -5,5 +5,4 @@ export const Container = styled.div`
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
`;
|
||||
|
@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
interface OptionIconProps {
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
function OptionIcon({ isDarkMode }: OptionIconProps): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="1rem"
|
||||
height="1rem"
|
||||
viewBox="0 0 52 52"
|
||||
enableBackground="new 0 0 52 52"
|
||||
fill={isDarkMode ? '#eee' : '#222'}
|
||||
>
|
||||
<path
|
||||
d="M20,44c0-3.3,2.7-6,6-6s6,2.7,6,6s-2.7,6-6,6S20,47.3,20,44z M20,26c0-3.3,2.7-6,6-6s6,2.7,6,6s-2.7,6-6,6
|
||||
S20,29.3,20,26z M20,8c0-3.3,2.7-6,6-6s6,2.7,6,6s-2.7,6-6,6S20,11.3,20,8z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default OptionIcon;
|
26
frontend/src/container/LogLiveTail/config.ts
Normal file
26
frontend/src/container/LogLiveTail/config.ts
Normal file
@ -0,0 +1,26 @@
|
||||
export const TIME_PICKER_OPTIONS = [
|
||||
{
|
||||
value: 5,
|
||||
label: '5m',
|
||||
},
|
||||
{
|
||||
value: 15,
|
||||
label: '15m',
|
||||
},
|
||||
{
|
||||
value: 30,
|
||||
label: '30m',
|
||||
},
|
||||
{
|
||||
value: 60,
|
||||
label: '1hr',
|
||||
},
|
||||
{
|
||||
value: 360,
|
||||
label: '6hrs',
|
||||
},
|
||||
{
|
||||
value: 720,
|
||||
label: '12hrs',
|
||||
},
|
||||
];
|
@ -1,7 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { green } from '@ant-design/colors';
|
||||
import { PauseOutlined, PlayCircleOutlined } from '@ant-design/icons';
|
||||
import { Button, Popover, Row, Select } from 'antd';
|
||||
import {
|
||||
MoreOutlined,
|
||||
PauseOutlined,
|
||||
PlayCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Button, Popover, Select, Space } from 'antd';
|
||||
import { LiveTail } from 'api/logs/livetail';
|
||||
import dayjs from 'dayjs';
|
||||
import { throttle } from 'lodash-es';
|
||||
@ -18,38 +21,11 @@ import { TLogsLiveTailState } from 'types/api/logs/liveTail';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import OptionIcon from './OptionIcon';
|
||||
import { TimePickerCard, TimePickerSelect } from './styles';
|
||||
import { TIME_PICKER_OPTIONS } from './config';
|
||||
import { StopContainer, TimePickerCard, TimePickerSelect } from './styles';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const TIME_PICKER_OPTIONS = [
|
||||
{
|
||||
value: 5,
|
||||
label: '5m',
|
||||
},
|
||||
{
|
||||
value: 15,
|
||||
label: '15m',
|
||||
},
|
||||
{
|
||||
value: 30,
|
||||
label: '30m',
|
||||
},
|
||||
{
|
||||
value: 60,
|
||||
label: '1hr',
|
||||
},
|
||||
{
|
||||
value: 360,
|
||||
label: '6hrs',
|
||||
},
|
||||
{
|
||||
value: 720,
|
||||
label: '12hrs',
|
||||
},
|
||||
];
|
||||
|
||||
function LogLiveTail(): JSX.Element {
|
||||
const {
|
||||
liveTail,
|
||||
@ -75,14 +51,12 @@ function LogLiveTail(): JSX.Element {
|
||||
type: PUSH_LIVE_TAIL_EVENT,
|
||||
payload: batchedEventsRef.current.reverse(),
|
||||
});
|
||||
// console.log('DISPATCH', batchedEventsRef.current.length);
|
||||
batchedEventsRef.current = [];
|
||||
}, 1500),
|
||||
[],
|
||||
);
|
||||
|
||||
const batchLiveLog = (e: { data: string }): void => {
|
||||
// console.log('EVENT BATCHED');
|
||||
batchedEventsRef.current.push(JSON.parse(e.data as string) as never);
|
||||
pushLiveLog();
|
||||
};
|
||||
@ -123,6 +97,7 @@ function LogLiveTail(): JSX.Element {
|
||||
if (liveTail === 'STOPPED') {
|
||||
liveTailSourceRef.current = null;
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [liveTail]);
|
||||
|
||||
const handleLiveTailStart = (): void => {
|
||||
@ -155,47 +130,39 @@ function LogLiveTail(): JSX.Element {
|
||||
),
|
||||
[dispatch, liveTail, liveTailStartRange],
|
||||
);
|
||||
|
||||
return (
|
||||
<TimePickerCard>
|
||||
<Row
|
||||
style={{ gap: '0.5rem', alignItems: 'center', justifyContent: 'center' }}
|
||||
>
|
||||
<div>
|
||||
{liveTail === 'PLAYING' ? (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={(): void => handleLiveTail('PAUSED')}
|
||||
title="Pause live tail"
|
||||
style={{ background: green[6] }}
|
||||
>
|
||||
Pause <PauseOutlined />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleLiveTailStart}
|
||||
title="Start live tail"
|
||||
>
|
||||
Go Live <PlayCircleOutlined />
|
||||
</Button>
|
||||
)}
|
||||
{liveTail !== 'STOPPED' && (
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={(): void => handleLiveTail('STOPPED')}
|
||||
title="Exit live tail"
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '0.8rem',
|
||||
width: '0.8rem',
|
||||
background: isDarkMode ? '#eee' : '#222',
|
||||
borderRadius: '0.1rem',
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Space size={0} align="center">
|
||||
{liveTail === 'PLAYING' ? (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={(): void => handleLiveTail('PAUSED')}
|
||||
title="Pause live tail"
|
||||
style={{ background: green[6] }}
|
||||
>
|
||||
<span>Pause</span>
|
||||
<PauseOutlined />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleLiveTailStart}
|
||||
title="Start live tail"
|
||||
>
|
||||
Go Live <PlayCircleOutlined />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{liveTail !== 'STOPPED' && (
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={(): void => handleLiveTail('STOPPED')}
|
||||
title="Exit live tail"
|
||||
>
|
||||
<StopContainer isDarkMode={isDarkMode} />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Popover
|
||||
placement="bottomRight"
|
||||
@ -203,18 +170,9 @@ function LogLiveTail(): JSX.Element {
|
||||
trigger="click"
|
||||
content={OptionsPopOverContent}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
padding: '0.3rem 0.4rem 0.3rem 0',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignContent: 'center',
|
||||
}}
|
||||
>
|
||||
<OptionIcon isDarkMode={isDarkMode} />
|
||||
</span>
|
||||
<MoreOutlined style={{ fontSize: 24 }} />
|
||||
</Popover>
|
||||
</Row>
|
||||
</Space>
|
||||
</TimePickerCard>
|
||||
);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import styled from 'styled-components';
|
||||
|
||||
export const TimePickerCard = styled(Card)`
|
||||
.ant-card-body {
|
||||
display: flex;
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
@ -10,3 +11,15 @@ export const TimePickerCard = styled(Card)`
|
||||
export const TimePickerSelect = styled(Select)`
|
||||
min-width: 100px;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
|
||||
export const StopContainer = styled.div<Props>`
|
||||
height: 0.8rem;
|
||||
width: 0.8rem;
|
||||
border-radius: 0.1rem;
|
||||
background-color: ${({ isDarkMode }): string =>
|
||||
isDarkMode ? '#fff' : '#000'};
|
||||
`;
|
||||
|
@ -1,66 +0,0 @@
|
||||
import { Divider, Row } from 'antd';
|
||||
import LogControls from 'container/LogControls';
|
||||
import LogDetailedView from 'container/LogDetailedView';
|
||||
import LogLiveTail from 'container/LogLiveTail';
|
||||
import LogsAggregate from 'container/LogsAggregate';
|
||||
import LogsFilters from 'container/LogsFilters';
|
||||
import SearchFilter from 'container/LogsSearchFilter';
|
||||
import LogsTable from 'container/LogsTable';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import React, { memo, useEffect } from 'react';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { GetLogsFields } from 'store/actions/logs/getFields';
|
||||
import AppActions from 'types/actions';
|
||||
import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs';
|
||||
|
||||
function Logs({ getLogsFields }: LogsProps): JSX.Element {
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: SET_SEARCH_QUERY_STRING,
|
||||
payload: urlQuery.get('q'),
|
||||
});
|
||||
}, [dispatch, urlQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
getLogsFields();
|
||||
}, [getLogsFields]);
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<Row style={{ justifyContent: 'center', alignItems: 'center' }}>
|
||||
<SearchFilter />
|
||||
<Divider type="vertical" style={{ height: '2rem' }} />
|
||||
<LogLiveTail />
|
||||
</Row>
|
||||
<LogsAggregate />
|
||||
<LogControls />
|
||||
<Divider style={{ margin: 0 }} />
|
||||
<Row gutter={20} style={{ flexWrap: 'nowrap' }}>
|
||||
<LogsFilters />
|
||||
<Divider type="vertical" style={{ height: '100%', margin: 0 }} />
|
||||
<LogsTable />
|
||||
</Row>
|
||||
<LogDetailedView />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type LogsProps = DispatchProps;
|
||||
|
||||
interface DispatchProps {
|
||||
getLogsFields: () => (dispatch: Dispatch<AppActions>) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
getLogsFields: bindActionCreators(GetLogsFields, dispatch),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(memo(Logs));
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { blue } from '@ant-design/colors';
|
||||
import Graph from 'components/Graph';
|
||||
import Spinner from 'components/Spinner';
|
||||
@ -16,9 +15,6 @@ import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import { Container } from './styles';
|
||||
|
||||
interface LogsAggregateProps {
|
||||
getLogsAggregate: (arg0: Parameters<typeof getLogsAggregate>[0]) => void;
|
||||
}
|
||||
function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element {
|
||||
const {
|
||||
searchFilter: { queryString },
|
||||
@ -42,18 +38,18 @@ function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element {
|
||||
clearInterval(reFetchIntervalRef.current);
|
||||
}
|
||||
reFetchIntervalRef.current = null;
|
||||
getLogsAggregate({
|
||||
timestampStart: minTime,
|
||||
timestampEnd: maxTime,
|
||||
step: getStep({
|
||||
start: minTime,
|
||||
end: maxTime,
|
||||
inputFormat: 'ns',
|
||||
}),
|
||||
q: queryString,
|
||||
...(idStart ? { idGt: idStart } : {}),
|
||||
...(idEnd ? { idLt: idEnd } : {}),
|
||||
});
|
||||
// getLogsAggregate({
|
||||
// timestampStart: minTime,
|
||||
// timestampEnd: maxTime,
|
||||
// step: getStep({
|
||||
// start: minTime,
|
||||
// end: maxTime,
|
||||
// inputFormat: 'ns',
|
||||
// }),
|
||||
// q: queryString,
|
||||
// ...(idStart ? { idGt: idStart } : {}),
|
||||
// ...(idEnd ? { idLt: idEnd } : {}),
|
||||
// });
|
||||
break;
|
||||
}
|
||||
|
||||
@ -89,18 +85,9 @@ function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getLogsAggregate, maxTime, minTime, liveTail]);
|
||||
|
||||
const data = {
|
||||
labels: logsAggregate.map((s) => new Date(s.timestamp / 1000000)),
|
||||
datasets: [
|
||||
{
|
||||
data: logsAggregate.map((s) => s.value),
|
||||
backgroundColor: blue[4],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{isLoadingAggregate ? (
|
||||
@ -108,7 +95,15 @@ function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element {
|
||||
) : (
|
||||
<Graph
|
||||
name="usage"
|
||||
data={data}
|
||||
data={{
|
||||
labels: logsAggregate.map((s) => new Date(s.timestamp / 1000000)),
|
||||
datasets: [
|
||||
{
|
||||
data: logsAggregate.map((s) => s.value),
|
||||
backgroundColor: blue[4],
|
||||
},
|
||||
],
|
||||
}}
|
||||
type="bar"
|
||||
containerHeight="100%"
|
||||
animate={false}
|
||||
@ -118,6 +113,10 @@ function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
interface LogsAggregateProps {
|
||||
getLogsAggregate: (arg0: Parameters<typeof getLogsAggregate>[0]) => void;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getLogsAggregate: (
|
||||
props: Parameters<typeof getLogsAggregate>[0],
|
||||
|
@ -21,9 +21,6 @@ import { CategoryContainer, Container, FieldContainer } from './styles';
|
||||
|
||||
const RESTRICTED_SELECTED_FIELDS = ['timestamp', 'id'];
|
||||
|
||||
interface LogsFiltersProps {
|
||||
getLogsFields: () => void;
|
||||
}
|
||||
function LogsFilters({ getLogsFields }: LogsFiltersProps): JSX.Element {
|
||||
const {
|
||||
fields: { interesting, selected },
|
||||
@ -150,4 +147,6 @@ const mapDispatchToProps = (
|
||||
getLogsFields: bindActionCreators(GetLogsFields, dispatch),
|
||||
});
|
||||
|
||||
type LogsFiltersProps = DispatchProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(memo(LogsFilters));
|
||||
|
@ -4,7 +4,7 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import { CloseOutlined, CloseSquareOutlined } from '@ant-design/icons';
|
||||
import { Button, Input, Select } from 'antd';
|
||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||
import {
|
||||
@ -19,12 +19,46 @@ import { AppState } from 'store/reducers';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { SearchFieldsProps } from '..';
|
||||
import FieldKey from '../FieldKey';
|
||||
import { QueryConditionContainer, QueryFieldContainer } from '../styles';
|
||||
import { createParsedQueryStructure } from '../utils';
|
||||
import { Container, QueryWrapper } from './styles';
|
||||
import { hashCode, parseQuery } from './utils';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
function QueryConditionField({
|
||||
query,
|
||||
queryIndex,
|
||||
onUpdate,
|
||||
style,
|
||||
}: QueryConditionFieldProps): JSX.Element {
|
||||
return (
|
||||
<QueryConditionContainer style={{ ...style }}>
|
||||
<Select
|
||||
defaultValue={
|
||||
(query as any).value &&
|
||||
(((query as any)?.value as any) as string).toUpperCase()
|
||||
}
|
||||
onChange={(e): void => {
|
||||
onUpdate({ ...query, value: e }, queryIndex);
|
||||
}}
|
||||
>
|
||||
{Object.values(ConditionalOperators).map((cond) => (
|
||||
<Option key={cond} value={cond} label={cond}>
|
||||
{cond}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</QueryConditionContainer>
|
||||
);
|
||||
}
|
||||
|
||||
QueryConditionField.defaultProps = {
|
||||
style: undefined,
|
||||
};
|
||||
|
||||
interface QueryFieldProps {
|
||||
query: Query;
|
||||
queryIndex: number;
|
||||
@ -140,41 +174,15 @@ interface QueryConditionFieldProps {
|
||||
query: { value: string | string[]; type: string }[];
|
||||
queryIndex: number;
|
||||
onUpdate: (arg0: unknown, arg1: number) => void;
|
||||
}
|
||||
function QueryConditionField({
|
||||
query,
|
||||
queryIndex,
|
||||
onUpdate,
|
||||
}: QueryConditionFieldProps): JSX.Element {
|
||||
return (
|
||||
<QueryConditionContainer>
|
||||
<Select
|
||||
defaultValue={
|
||||
(query as any).value &&
|
||||
(((query as any)?.value as any) as string).toUpperCase()
|
||||
}
|
||||
onChange={(e): void => {
|
||||
onUpdate({ ...query, value: e }, queryIndex);
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
{Object.values(ConditionalOperators).map((cond) => (
|
||||
<Option key={cond} value={cond} label={cond}>
|
||||
{cond}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</QueryConditionContainer>
|
||||
);
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export type Query = { value: string | string[]; type: string }[];
|
||||
|
||||
function QueryBuilder({
|
||||
updateParsedQuery,
|
||||
}: {
|
||||
updateParsedQuery: (arg0: unknown) => void;
|
||||
}): JSX.Element {
|
||||
onDropDownToggleHandler,
|
||||
}: SearchFieldsProps): JSX.Element {
|
||||
const {
|
||||
searchFilter: { parsedQuery },
|
||||
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
||||
@ -233,19 +241,16 @@ function QueryBuilder({
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CategoryHeading>LOG QUERY BUILDER</CategoryHeading>
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '80px 1fr',
|
||||
margin: '0.5rem 0',
|
||||
}}
|
||||
>
|
||||
{QueryUI()}
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<Container isMargin={generatedQueryStructure.length === 0}>
|
||||
<CategoryHeading>LOG QUERY BUILDER</CategoryHeading>
|
||||
<CloseSquareOutlined onClick={onDropDownToggleHandler(false)} />
|
||||
</Container>
|
||||
|
||||
<QueryWrapper>{QueryUI()}</QueryWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,17 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface Props {
|
||||
isMargin: boolean;
|
||||
}
|
||||
export const Container = styled.div<Props>`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-bottom: ${(props): string => (props.isMargin ? '2rem' : '0')};
|
||||
`;
|
||||
|
||||
export const QueryWrapper = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr;
|
||||
margin: 0.5rem 0px;
|
||||
`;
|
@ -1,6 +1,6 @@
|
||||
import { Button } from 'antd';
|
||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||
import { map } from 'lodash-es';
|
||||
import map from 'lodash-es/map';
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
|
@ -2,14 +2,23 @@ import React from 'react';
|
||||
|
||||
import QueryBuilder from './QueryBuilder/QueryBuilder';
|
||||
import Suggestions from './Suggestions';
|
||||
import { QueryFields } from './utils';
|
||||
|
||||
interface SearchFieldsProps {
|
||||
updateParsedQuery: () => void;
|
||||
export interface SearchFieldsProps {
|
||||
updateParsedQuery: (query: QueryFields[]) => void;
|
||||
onDropDownToggleHandler: (value: boolean) => VoidFunction;
|
||||
}
|
||||
function SearchFields({ updateParsedQuery }: SearchFieldsProps): JSX.Element {
|
||||
|
||||
function SearchFields({
|
||||
updateParsedQuery,
|
||||
onDropDownToggleHandler,
|
||||
}: SearchFieldsProps): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<QueryBuilder updateParsedQuery={updateParsedQuery} />
|
||||
<QueryBuilder
|
||||
onDropDownToggleHandler={onDropDownToggleHandler}
|
||||
updateParsedQuery={updateParsedQuery}
|
||||
/>
|
||||
<Suggestions />
|
||||
</>
|
||||
);
|
||||
|
@ -9,12 +9,13 @@ export const QueryFieldContainer = styled.div`
|
||||
align-items: center;
|
||||
border-radius: 0.25rem;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
&:hover {
|
||||
background: ${blue[6]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const QueryConditionContainer = styled.div`
|
||||
padding: 0.25rem 0rem;
|
||||
margin: 0.1rem 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
`;
|
||||
|
@ -1,11 +1,8 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { CloseSquareOutlined } from '@ant-design/icons';
|
||||
import { Button, Input } from 'antd';
|
||||
import useClickOutside from 'hooks/useClickOutside';
|
||||
import { Input, InputRef, Popover } from 'antd';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import getStep from 'lib/getStep';
|
||||
import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-use';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { getLogs } from 'store/actions/logs/getLogs';
|
||||
@ -17,17 +14,9 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import SearchFields from './SearchFields';
|
||||
import { DropDownContainer } from './styles';
|
||||
import { Container, DropDownContainer } from './styles';
|
||||
import { useSearchParser } from './useSearchParser';
|
||||
|
||||
const { Search } = Input;
|
||||
|
||||
interface SearchFilterProps {
|
||||
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
|
||||
getLogsAggregate: (
|
||||
props: Parameters<typeof getLogsAggregate>[0],
|
||||
) => ReturnType<typeof getLogsAggregate>;
|
||||
}
|
||||
function SearchFilter({
|
||||
getLogs,
|
||||
getLogsAggregate,
|
||||
@ -38,6 +27,14 @@ function SearchFilter({
|
||||
updateQueryString,
|
||||
} = useSearchParser();
|
||||
const [showDropDown, setShowDropDown] = useState(false);
|
||||
const searchRef = useRef<InputRef>(null);
|
||||
|
||||
const onDropDownToggleHandler = useCallback(
|
||||
(value: boolean) => (): void => {
|
||||
setShowDropDown(value);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const { logLinesPerPage, idEnd, idStart, liveTail } = useSelector<
|
||||
AppState,
|
||||
@ -48,117 +45,104 @@ function SearchFilter({
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const searchComponentRef = useRef<HTMLDivElement>(null);
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
useClickOutside(searchComponentRef, (e: HTMLElement) => {
|
||||
// using this hack as overlay span is voilating this condition
|
||||
if (
|
||||
e.nodeName === 'svg' ||
|
||||
e.nodeName === 'path' ||
|
||||
e.nodeName === 'span' ||
|
||||
e.nodeName === 'button'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const handleSearch = useCallback(
|
||||
(customQuery) => {
|
||||
if (liveTail === 'PLAYING') {
|
||||
dispatch({
|
||||
type: TOGGLE_LIVE_TAIL,
|
||||
payload: 'PAUSED',
|
||||
});
|
||||
setTimeout(
|
||||
() =>
|
||||
dispatch({
|
||||
type: TOGGLE_LIVE_TAIL,
|
||||
payload: liveTail,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
} else {
|
||||
getLogs({
|
||||
q: customQuery,
|
||||
limit: logLinesPerPage,
|
||||
orderBy: 'timestamp',
|
||||
order: 'desc',
|
||||
timestampStart: minTime,
|
||||
timestampEnd: maxTime,
|
||||
...(idStart ? { idGt: idStart } : {}),
|
||||
...(idEnd ? { idLt: idEnd } : {}),
|
||||
});
|
||||
|
||||
if (
|
||||
e.nodeName === 'DIV' &&
|
||||
![
|
||||
'ant-empty-image',
|
||||
'ant-select-item',
|
||||
'ant-col',
|
||||
'ant-select-item-option-content',
|
||||
'ant-select-item-option-active',
|
||||
].find((p) => p.indexOf(e.className) !== -1) &&
|
||||
!(e.ariaSelected === 'true') &&
|
||||
showDropDown
|
||||
) {
|
||||
setShowDropDown(false);
|
||||
}
|
||||
});
|
||||
const { search } = useLocation();
|
||||
const dispatch = useDispatch();
|
||||
const handleSearch = (customQuery = ''): void => {
|
||||
if (liveTail === 'PLAYING') {
|
||||
dispatch({
|
||||
type: TOGGLE_LIVE_TAIL,
|
||||
payload: 'PAUSED',
|
||||
});
|
||||
setTimeout(
|
||||
() =>
|
||||
dispatch({
|
||||
type: TOGGLE_LIVE_TAIL,
|
||||
payload: liveTail,
|
||||
getLogsAggregate({
|
||||
timestampStart: minTime,
|
||||
timestampEnd: maxTime,
|
||||
step: getStep({
|
||||
start: minTime,
|
||||
end: maxTime,
|
||||
inputFormat: 'ns',
|
||||
}),
|
||||
0,
|
||||
);
|
||||
} else {
|
||||
getLogs({
|
||||
q: customQuery || queryString,
|
||||
limit: logLinesPerPage,
|
||||
orderBy: 'timestamp',
|
||||
order: 'desc',
|
||||
timestampStart: minTime,
|
||||
timestampEnd: maxTime,
|
||||
...(idStart ? { idGt: idStart } : {}),
|
||||
...(idEnd ? { idLt: idEnd } : {}),
|
||||
});
|
||||
q: customQuery,
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
dispatch,
|
||||
getLogs,
|
||||
getLogsAggregate,
|
||||
idEnd,
|
||||
idStart,
|
||||
liveTail,
|
||||
logLinesPerPage,
|
||||
maxTime,
|
||||
minTime,
|
||||
],
|
||||
);
|
||||
|
||||
getLogsAggregate({
|
||||
timestampStart: minTime,
|
||||
timestampEnd: maxTime,
|
||||
step: getStep({
|
||||
start: minTime,
|
||||
end: maxTime,
|
||||
inputFormat: 'ns',
|
||||
}),
|
||||
q: customQuery || queryString,
|
||||
});
|
||||
}
|
||||
setShowDropDown(false);
|
||||
};
|
||||
|
||||
const urlQuery = useMemo(() => {
|
||||
return new URLSearchParams(search);
|
||||
}, [search]);
|
||||
const urlQuery = useUrlQuery();
|
||||
const urlQueryString = urlQuery.get('q');
|
||||
|
||||
useEffect(() => {
|
||||
const urlQueryString = urlQuery.get('q');
|
||||
if (urlQueryString !== null) handleSearch(urlQueryString);
|
||||
}, []);
|
||||
handleSearch(urlQueryString || '');
|
||||
}, [handleSearch, urlQueryString]);
|
||||
|
||||
return (
|
||||
<div ref={searchComponentRef} style={{ flex: 1 }}>
|
||||
<Search
|
||||
placeholder="Search Filter"
|
||||
onFocus={(): void => setShowDropDown(true)}
|
||||
value={queryString}
|
||||
onChange={(e): void => {
|
||||
updateQueryString(e.target.value);
|
||||
}}
|
||||
onSearch={handleSearch}
|
||||
/>
|
||||
<div style={{ position: 'relative' }}>
|
||||
{showDropDown && (
|
||||
<Container>
|
||||
<Popover
|
||||
placement="bottom"
|
||||
content={
|
||||
<DropDownContainer>
|
||||
<Button
|
||||
type="text"
|
||||
onClick={(): void => setShowDropDown(false)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
}}
|
||||
>
|
||||
<CloseSquareOutlined />
|
||||
</Button>
|
||||
<SearchFields updateParsedQuery={updateParsedQuery as never} />
|
||||
<SearchFields
|
||||
onDropDownToggleHandler={onDropDownToggleHandler}
|
||||
updateParsedQuery={updateParsedQuery as never}
|
||||
/>
|
||||
</DropDownContainer>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
trigger="click"
|
||||
overlayInnerStyle={{
|
||||
width: `${searchRef?.current?.input?.offsetWidth || 0}px`,
|
||||
}}
|
||||
visible={showDropDown}
|
||||
destroyTooltipOnHide
|
||||
onVisibleChange={(value): void => {
|
||||
onDropDownToggleHandler(value)();
|
||||
}}
|
||||
>
|
||||
<Input.Search
|
||||
ref={searchRef}
|
||||
placeholder="Search Filter"
|
||||
value={queryString}
|
||||
onChange={(e): void => {
|
||||
updateQueryString(e.target.value);
|
||||
}}
|
||||
allowClear
|
||||
onSearch={handleSearch}
|
||||
/>
|
||||
</Popover>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getLogs: (
|
||||
props: Parameters<typeof getLogs>[0],
|
||||
@ -168,6 +152,8 @@ interface DispatchProps {
|
||||
) => (dispatch: Dispatch<AppActions>) => void;
|
||||
}
|
||||
|
||||
type SearchFilterProps = DispatchProps;
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
@ -175,4 +161,4 @@ const mapDispatchToProps = (
|
||||
getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(memo(SearchFilter));
|
||||
export default connect(null, mapDispatchToProps)(SearchFilter);
|
||||
|
@ -2,11 +2,13 @@ import { Card } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const DropDownContainer = styled(Card)`
|
||||
top: 0.5rem;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
.ant-card-body {
|
||||
padding: 0.8rem;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
`;
|
||||
|
@ -23,10 +23,12 @@ export function useSearchParser(): {
|
||||
|
||||
const updateQueryString = useCallback(
|
||||
(updatedQueryString) => {
|
||||
history.push({
|
||||
pathname: history.location.pathname,
|
||||
search: updatedQueryString ? `?q=${updatedQueryString}` : '',
|
||||
});
|
||||
if (updatedQueryString) {
|
||||
history.push({
|
||||
pathname: history.location.pathname,
|
||||
search: updatedQueryString ? `?q=${updatedQueryString}` : '',
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: SET_SEARCH_QUERY_STRING,
|
||||
|
@ -3,48 +3,18 @@ import { Typography } from 'antd';
|
||||
import LogItem from 'components/Logs/LogItem';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { map } from 'lodash-es';
|
||||
import React, { memo, useEffect } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { getLogs } from 'store/actions/logs/getLogs';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import { Container, Heading } from './styles';
|
||||
|
||||
function LogsTable({ getLogs }: LogsTableProps): JSX.Element {
|
||||
const {
|
||||
searchFilter: { queryString },
|
||||
logs,
|
||||
logLinesPerPage,
|
||||
idEnd,
|
||||
idStart,
|
||||
isLoading,
|
||||
liveTail,
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
function LogsTable(): JSX.Element {
|
||||
const { logs, isLoading, liveTail } = useSelector<AppState, ILogsReducer>(
|
||||
(state) => state.logs,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (liveTail === 'STOPPED')
|
||||
getLogs({
|
||||
q: queryString,
|
||||
limit: logLinesPerPage,
|
||||
orderBy: 'timestamp',
|
||||
order: 'desc',
|
||||
timestampStart: minTime,
|
||||
timestampEnd: maxTime,
|
||||
...(idStart ? { idGt: idStart } : {}),
|
||||
...(idEnd ? { idLt: idEnd } : {}),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getLogs, idEnd, idStart, logLinesPerPage, maxTime, minTime, liveTail]);
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner height={20} tip="Getting Logs" />;
|
||||
}
|
||||
@ -72,20 +42,4 @@ function LogsTable({ getLogs }: LogsTableProps): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getLogs: (
|
||||
props: Parameters<typeof getLogs>[0],
|
||||
) => (dispatch: Dispatch<AppActions>) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
getLogs: bindActionCreators(getLogs, dispatch),
|
||||
});
|
||||
|
||||
interface LogsTableProps {
|
||||
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(memo(LogsTable));
|
||||
export default memo(LogsTable);
|
||||
|
@ -1,8 +1,72 @@
|
||||
import Logs from 'container/Logs';
|
||||
import React from 'react';
|
||||
import { Divider, Row } from 'antd';
|
||||
import LogControls from 'container/LogControls';
|
||||
import LogDetailedView from 'container/LogDetailedView';
|
||||
import LogLiveTail from 'container/LogLiveTail';
|
||||
import LogsAggregate from 'container/LogsAggregate';
|
||||
import LogsFilters from 'container/LogsFilters';
|
||||
import LogsSearchFilter from 'container/LogsSearchFilter';
|
||||
import LogsTable from 'container/LogsTable';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import React, { memo, useEffect } from 'react';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { GetLogsFields } from 'store/actions/logs/getFields';
|
||||
import AppActions from 'types/actions';
|
||||
import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs';
|
||||
|
||||
function LogsHome(): JSX.Element {
|
||||
return <Logs />;
|
||||
import SpaceContainer from './styles';
|
||||
|
||||
function Logs({ getLogsFields }: LogsProps): JSX.Element {
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: SET_SEARCH_QUERY_STRING,
|
||||
payload: urlQuery.get('q'),
|
||||
});
|
||||
}, [dispatch, urlQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
getLogsFields();
|
||||
}, [getLogsFields]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SpaceContainer
|
||||
split={<Divider type="vertical" />}
|
||||
align="center"
|
||||
direction="horizontal"
|
||||
>
|
||||
<LogsSearchFilter />
|
||||
<LogLiveTail />
|
||||
</SpaceContainer>
|
||||
|
||||
<LogsAggregate />
|
||||
<LogControls />
|
||||
<Divider plain orientationMargin={1} />
|
||||
<Row gutter={20} wrap={false}>
|
||||
<LogsFilters />
|
||||
<Divider type="vertical" />
|
||||
<LogsTable />
|
||||
</Row>
|
||||
<LogDetailedView />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default LogsHome;
|
||||
type LogsProps = DispatchProps;
|
||||
|
||||
interface DispatchProps {
|
||||
getLogsFields: () => (dispatch: Dispatch<AppActions>) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
getLogsFields: bindActionCreators(GetLogsFields, dispatch),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(memo(Logs));
|
||||
|
10
frontend/src/pages/Logs/styles.ts
Normal file
10
frontend/src/pages/Logs/styles.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Space } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const SpaceContainer = styled(Space)`
|
||||
.ant-space-item:nth-child(1) {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export default SpaceContainer;
|
@ -99,15 +99,14 @@ export const LogsReducer = (
|
||||
}
|
||||
|
||||
case ADD_SEARCH_FIELD_QUERY_STRING: {
|
||||
const updatedQueryString =
|
||||
state.searchFilter.queryString ||
|
||||
`${
|
||||
state.searchFilter.queryString && state.searchFilter.queryString.length > 0
|
||||
? ' and '
|
||||
: ''
|
||||
}${action.payload}`;
|
||||
const updatedQueryString = `${state?.searchFilter?.queryString || ''}${
|
||||
state.searchFilter.queryString && state.searchFilter.queryString.length > 0
|
||||
? ' and '
|
||||
: ''
|
||||
}${action.payload}`;
|
||||
|
||||
const updatedParsedQuery = parseQuery(updatedQueryString);
|
||||
console.log({ updatedParsedQuery, updatedQueryString, action });
|
||||
return {
|
||||
...state,
|
||||
searchFilter: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user