fix: eslint and tsc fixes for logs (#1527)

* fix: eslint and tsc fixes for logs

* chore: remove package-lock file
This commit is contained in:
Pranshu Chittora 2022-08-19 17:16:04 +05:30 committed by GitHub
parent 32fba00aa8
commit f48a884f90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 2373 additions and 2264 deletions

View File

@ -126,6 +126,8 @@
"@types/copy-webpack-plugin": "^8.0.1", "@types/copy-webpack-plugin": "^8.0.1",
"@types/d3": "^6.2.0", "@types/d3": "^6.2.0",
"@types/d3-tip": "^3.5.5", "@types/d3-tip": "^3.5.5",
"@types/event-source-polyfill": "^1.0.0",
"@types/flat": "^5.0.2",
"@types/jest": "^27.5.1", "@types/jest": "^27.5.1",
"@types/lodash-es": "^4.17.4", "@types/lodash-es": "^4.17.4",
"@types/mini-css-extract-plugin": "^2.5.1", "@types/mini-css-extract-plugin": "^2.5.1",

View File

@ -4,7 +4,7 @@ import { ENVIRONMENT } from 'constants/env';
import { LOCALSTORAGE } from 'constants/localStorage'; import { LOCALSTORAGE } from 'constants/localStorage';
import { EventSourcePolyfill } from 'event-source-polyfill'; import { EventSourcePolyfill } from 'event-source-polyfill';
export const LiveTail = (queryParams) => { export const LiveTail = (queryParams: string): EventSourcePolyfill => {
const dict = { const dict = {
headers: { headers: {
Authorization: `Bearer ${getLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN)}`, Authorization: `Bearer ${getLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN)}`,

View File

@ -1,4 +1,4 @@
import { Button, Popover, Tag, Tooltip } from 'antd'; import { Button, Popover } from 'antd';
import getStep from 'lib/getStep'; import getStep from 'lib/getStep';
import { generateFilterQuery } from 'lib/logs/generateFilterQuery'; import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
import React, { memo, useCallback, useMemo } from 'react'; import React, { memo, useCallback, useMemo } from 'react';
@ -11,15 +11,24 @@ import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { SET_SEARCH_QUERY_STRING, TOGGLE_LIVE_TAIL } from 'types/actions/logs'; import { SET_SEARCH_QUERY_STRING, TOGGLE_LIVE_TAIL } from 'types/actions/logs';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
interface AddToQueryHOCProps {
fieldKey: string;
fieldValue: string;
children: React.ReactNode;
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
getLogsAggregate: (
props: Parameters<typeof getLogsAggregate>[0],
) => ReturnType<typeof getLogsAggregate>;
}
function AddToQueryHOC({ function AddToQueryHOC({
fieldKey, fieldKey,
fieldValue, fieldValue,
children, children,
getLogs, getLogs,
getLogsAggregate, getLogsAggregate,
}) { }: AddToQueryHOCProps): JSX.Element {
const { const {
searchFilter: { queryString }, searchFilter: { queryString },
logLinesPerPage, logLinesPerPage,
@ -72,9 +81,7 @@ function AddToQueryHOC({
...(idStart ? { idGt: idStart } : {}), ...(idStart ? { idGt: idStart } : {}),
...(idEnd ? { idLt: idEnd } : {}), ...(idEnd ? { idLt: idEnd } : {}),
}); });
} } else if (liveTail === 'PLAYING') {
else if (liveTail === 'PLAYING') {
dispatch({ dispatch({
type: TOGGLE_LIVE_TAIL, type: TOGGLE_LIVE_TAIL,
payload: 'PAUSED', payload: 'PAUSED',
@ -88,6 +95,7 @@ function AddToQueryHOC({
0, 0,
); );
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
dispatch, dispatch,
generatedQuery, generatedQuery,
@ -121,8 +129,12 @@ function AddToQueryHOC({
} }
interface DispatchProps { interface DispatchProps {
getLogs: () => (dispatch: Dispatch<AppActions>) => void; getLogs: (
getLogsAggregate: () => (dispatch: Dispatch<AppActions>) => void; props: Parameters<typeof getLogs>[0],
) => (dispatch: Dispatch<AppActions>) => void;
getLogsAggregate: (
props: Parameters<typeof getLogsAggregate>[0],
) => (dispatch: Dispatch<AppActions>) => void;
} }
const mapDispatchToProps = ( const mapDispatchToProps = (

View File

@ -1,19 +1,28 @@
import { Button, Popover, Tooltip } from 'antd'; import { Popover } from 'antd';
import React from 'react'; import React from 'react';
import { useCopyToClipboard } from 'react-use'; import { useCopyToClipboard } from 'react-use';
function CopyClipboardHOC({ textToCopy, children }) { interface CopyClipboardHOCProps {
const [_state, setCopy] = useCopyToClipboard(); textToCopy: string;
children: React.ReactNode;
}
function CopyClipboardHOC({
textToCopy,
children,
}: CopyClipboardHOCProps): JSX.Element {
const [, setCopy] = useCopyToClipboard();
return ( return (
<span <span
style={{ style={{
margin: 0, margin: 0,
padding: 0, padding: 0,
cursor: 'pointer' cursor: 'pointer',
}} }}
onClick={() => setCopy(textToCopy)} onClick={(): void => setCopy(textToCopy)}
onKeyDown={(): void => setCopy(textToCopy)}
role="button" role="button"
tabIndex={0}
> >
<Popover <Popover
placement="top" placement="top"

View File

@ -1,22 +1,26 @@
import { blue, grey, orange } from '@ant-design/colors'; import { blue, grey, orange } from '@ant-design/colors';
import { CopyFilled, CopyrightCircleFilled, ExpandAltOutlined } from '@ant-design/icons'; import { CopyFilled, ExpandAltOutlined } from '@ant-design/icons';
import { Button, Card, Divider, Row, Typography } from 'antd'; import { Button, Divider, Row, Typography } from 'antd';
import { map } from 'd3'; import { map } from 'd3';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { FlatLogData } from 'lib/logs/flatLogData'; import { FlatLogData } from 'lib/logs/flatLogData';
import { flatMap, flatMapDeep } from 'lodash-es';
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useCopyToClipboard } from 'react-use'; import { useCopyToClipboard } from 'react-use';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs'; import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
import ILogsReducer from 'types/reducer/logs'; import { ILog } from 'types/api/logs/log';
import { ILogsReducer } from 'types/reducer/logs';
import AddToQueryHOC from '../AddToQueryHOC'; import AddToQueryHOC from '../AddToQueryHOC';
import CopyClipboardHOC from '../CopyClipboardHOC'; import CopyClipboardHOC from '../CopyClipboardHOC';
import { Container } from './styles'; import { Container } from './styles';
function LogGeneralField({ fieldKey, fieldValue }) { interface LogFieldProps {
fieldKey: string;
fieldValue: string;
}
function LogGeneralField({ fieldKey, fieldValue }: LogFieldProps): JSX.Element {
return ( return (
<div <div
style={{ style={{
@ -35,8 +39,10 @@ function LogGeneralField({ fieldKey, fieldValue }) {
</div> </div>
); );
} }
function LogSelectedField({
function LogSelectedField({ fieldKey = '', fieldValue = '' }) { fieldKey = '',
fieldValue = '',
}: LogFieldProps): JSX.Element {
return ( return (
<div <div
style={{ style={{
@ -66,13 +72,16 @@ function LogSelectedField({ fieldKey = '', fieldValue = '' }) {
); );
} }
function LogItem({ logData }) { interface LogItemProps {
logData: ILog;
}
function LogItem({ logData }: LogItemProps): JSX.Element {
const { const {
fields: { selected }, fields: { selected },
} = useSelector<AppState, ILogsReducer>((state) => state.logs); } = useSelector<AppState, ILogsReducer>((state) => state.logs);
const dispatch = useDispatch(); const dispatch = useDispatch();
const flattenLogData = useMemo(() => FlatLogData(logData), [logData]); const flattenLogData = useMemo(() => FlatLogData(logData), [logData]);
const [_state, setCopy] = useCopyToClipboard(); const [, setCopy] = useCopyToClipboard();
const handleDetailedView = useCallback(() => { const handleDetailedView = useCallback(() => {
dispatch({ dispatch({
@ -81,22 +90,28 @@ function LogItem({ logData }) {
}); });
}, [dispatch, logData]); }, [dispatch, logData]);
const handleCopyJSON = () => { const handleCopyJSON = (): void => {
setCopy(JSON.stringify(logData, null, 2)) setCopy(JSON.stringify(logData, null, 2));
} };
return ( return (
<Container> <Container>
<div style={{ maxWidth: '100%' }}> <div style={{ maxWidth: '100%' }}>
<div> <div>
{'{'} {'{'}
<div style={{ marginLeft: '0.5rem' }}> <div style={{ marginLeft: '0.5rem' }}>
<LogGeneralField fieldKey="log" fieldValue={flattenLogData.body} /> <LogGeneralField
fieldKey="log"
fieldValue={flattenLogData.body as never}
/>
{flattenLogData.stream && ( {flattenLogData.stream && (
<LogGeneralField fieldKey="stream" fieldValue={flattenLogData.stream} /> <LogGeneralField
fieldKey="stream"
fieldValue={flattenLogData.stream as never}
/>
)} )}
<LogGeneralField <LogGeneralField
fieldKey="timestamp" fieldKey="timestamp"
fieldValue={dayjs(flattenLogData.timestamp / 1e6).format()} fieldValue={dayjs((flattenLogData.timestamp as never) / 1e6).format()}
/> />
</div> </div>
{'}'} {'}'}
@ -107,7 +122,7 @@ function LogItem({ logData }) {
<LogSelectedField <LogSelectedField
key={field.name} key={field.name}
fieldKey={field.name} fieldKey={field.name}
fieldValue={flattenLogData[field.name]} fieldValue={flattenLogData[field.name] as never}
/> />
) : null; ) : null;
})} })}

View File

@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { LoadingOutlined } from '@ant-design/icons'; import { LoadingOutlined } from '@ant-design/icons';
import { import {
Button, Button,

View File

@ -87,7 +87,7 @@ function Retention({
<Col span={12} style={{ display: 'flex' }}> <Col span={12} style={{ display: 'flex' }}>
<RetentionFieldLabel>{text}</RetentionFieldLabel> <RetentionFieldLabel>{text}</RetentionFieldLabel>
</Col> </Col>
<Row span={12} justify="end"> <Row justify="end">
<RetentionFieldInputContainer> <RetentionFieldInputContainer>
<Input <Input
value={selectedValue && selectedValue >= 0 ? selectedValue : ''} value={selectedValue && selectedValue >= 0 ? selectedValue : ''}

View File

@ -1,5 +1,4 @@
import { import {
ArrowLeftOutlined,
FastBackwardOutlined, FastBackwardOutlined,
LeftOutlined, LeftOutlined,
RightOutlined, RightOutlined,
@ -19,7 +18,7 @@ import {
SET_LOG_LINES_PER_PAGE, SET_LOG_LINES_PER_PAGE,
} from 'types/actions/logs'; } from 'types/actions/logs';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import { Container } from './styles'; import { Container } from './styles';
@ -27,7 +26,10 @@ const { Option } = Select;
const ITEMS_PER_PAGE_OPTIONS = [25, 50, 100, 200]; const ITEMS_PER_PAGE_OPTIONS = [25, 50, 100, 200];
function LogControls({ getLogs }) { interface LogControlsProps {
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
}
function LogControls({ getLogs }: LogControlsProps): JSX.Element | null {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
); );
@ -40,14 +42,14 @@ function LogControls({ getLogs }) {
} = useSelector<AppState, ILogsReducer>((state) => state.logs); } = useSelector<AppState, ILogsReducer>((state) => state.logs);
const dispatch = useDispatch(); const dispatch = useDispatch();
const handleLogLinesPerPageChange = (e: number) => { const handleLogLinesPerPageChange = (e: number): void => {
dispatch({ dispatch({
type: SET_LOG_LINES_PER_PAGE, type: SET_LOG_LINES_PER_PAGE,
payload: e, payload: e,
}); });
}; };
const handleGoToLatest = () => { const handleGoToLatest = (): void => {
dispatch({ dispatch({
type: RESET_ID_START_AND_END, type: RESET_ID_START_AND_END,
}); });
@ -65,12 +67,12 @@ function LogControls({ getLogs }) {
}); });
}; };
const handleNavigatePrevious = () => { const handleNavigatePrevious = (): void => {
dispatch({ dispatch({
type: GET_PREVIOUS_LOG_LINES, type: GET_PREVIOUS_LOG_LINES,
}); });
}; };
const handleNavigateNext = () => { const handleNavigateNext = (): void => {
dispatch({ dispatch({
type: GET_NEXT_LOG_LINES, type: GET_NEXT_LOG_LINES,
}); });
@ -105,7 +107,9 @@ function LogControls({ getLogs }) {
} }
interface DispatchProps { interface DispatchProps {
getLogs: () => (dispatch: Dispatch<AppActions>) => void; getLogs: (
props: Parameters<typeof getLogs>[0],
) => (dispatch: Dispatch<AppActions>) => void;
} }
const mapDispatchToProps = ( const mapDispatchToProps = (

View File

@ -2,7 +2,7 @@ import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons';
import { Button, Col, Popover } from 'antd'; import { Button, Col, Popover } from 'antd';
import getStep from 'lib/getStep'; import getStep from 'lib/getStep';
import { generateFilterQuery } from 'lib/logs/generateFilterQuery'; import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
import React, { Dispatch, memo, useCallback, useMemo } from 'react'; import React, { memo, useMemo } from 'react';
import { connect, useDispatch, useSelector } from 'react-redux'; import { connect, useDispatch, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
@ -12,9 +12,9 @@ import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { SET_SEARCH_QUERY_STRING, TOGGLE_LIVE_TAIL } from 'types/actions/logs'; import { SET_SEARCH_QUERY_STRING, TOGGLE_LIVE_TAIL } from 'types/actions/logs';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
const removeJSONStringifyQuotes = (s: string) => { const removeJSONStringifyQuotes = (s: string): string => {
if (!s || !s.length) { if (!s || !s.length) {
return s; return s;
} }
@ -24,7 +24,21 @@ const removeJSONStringifyQuotes = (s: string) => {
} }
return s; return s;
}; };
function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
interface ActionItemProps {
fieldKey: string;
fieldValue: string;
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
getLogsAggregate: (
props: Parameters<typeof getLogsAggregate>[0],
) => ReturnType<typeof getLogsAggregate>;
}
function ActionItem({
fieldKey,
fieldValue,
getLogs,
getLogsAggregate,
}: ActionItemProps): JSX.Element | unknown {
const { const {
searchFilter: { queryString }, searchFilter: { queryString },
logLinesPerPage, logLinesPerPage,
@ -38,7 +52,7 @@ function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
(state) => state.globalTime, (state) => state.globalTime,
); );
const handleQueryAdd = (newQueryString) => { const handleQueryAdd = (newQueryString: string): void => {
let updatedQueryString = queryString || ''; let updatedQueryString = queryString || '';
if (updatedQueryString.length === 0) { if (updatedQueryString.length === 0) {
@ -94,7 +108,7 @@ function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
<Button <Button
type="text" type="text"
size="small" size="small"
onClick={() => onClick={(): void =>
handleQueryAdd( handleQueryAdd(
generateFilterQuery({ generateFilterQuery({
fieldKey, fieldKey,
@ -110,7 +124,7 @@ function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
<Button <Button
type="text" type="text"
size="small" size="small"
onClick={() => onClick={(): void =>
handleQueryAdd( handleQueryAdd(
generateFilterQuery({ generateFilterQuery({
fieldKey, fieldKey,
@ -124,7 +138,8 @@ function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
</Button> </Button>
</Col> </Col>
), ),
[], // eslint-disable-next-line react-hooks/exhaustive-deps
[fieldKey, validatedFieldValue],
); );
return ( return (
<Popover placement="bottomLeft" content={PopOverMenuContent} trigger="click"> <Popover placement="bottomLeft" content={PopOverMenuContent} trigger="click">
@ -134,10 +149,11 @@ function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
</Popover> </Popover>
); );
} }
interface DispatchProps { interface DispatchProps {
getLogs: () => (dispatch: Dispatch<AppActions>) => void; getLogs: (props: Parameters<typeof getLogs>[0]) => (dispatch: never) => void;
getLogsAggregate: () => (dispatch: Dispatch<AppActions>) => void; getLogsAggregate: (
props: Parameters<typeof getLogsAggregate>[0],
) => (dispatch: never) => void;
} }
const mapDispatchToProps = ( const mapDispatchToProps = (
@ -147,4 +163,5 @@ const mapDispatchToProps = (
getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch), getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch),
}); });
export default connect(null, mapDispatchToProps)(memo(ActionItem)); // eslint-disable-next-line @typescript-eslint/no-explicit-any
export default connect(null, mapDispatchToProps)(memo(ActionItem as any));

View File

@ -4,31 +4,41 @@ import { Button, Row } from 'antd';
import Editor from 'components/Editor'; import Editor from 'components/Editor';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useCopyToClipboard } from 'react-use'; import { useCopyToClipboard } from 'react-use';
import { ILog } from 'types/api/logs/log';
function JSONView({ logData }) { interface JSONViewProps {
const [_state, copyToClipboard] = useCopyToClipboard(); logData: ILog;
const LogJsonData = useMemo(() => JSON.stringify(logData, null, 2), [logData]); }
return ( function JSONView({ logData }: JSONViewProps): JSX.Element {
<div> const [, copyToClipboard] = useCopyToClipboard();
<Row const LogJsonData = useMemo(() => JSON.stringify(logData, null, 2), [logData]);
style={{ return (
justifyContent: 'flex-end', <div>
margin: '0.5rem 0', <Row
}} style={{
> justifyContent: 'flex-end',
<Button margin: '0.5rem 0',
size="small" }}
type="text" >
onClick={(): void => copyToClipboard(LogJsonData)} <Button
> size="small"
<CopyFilled /> <span style={{ color: blue[5] }}>Copy to Clipboard</span> type="text"
</Button> onClick={(): void => copyToClipboard(LogJsonData)}
</Row> >
<div style={{ marginTop: '0.5rem' }}> <CopyFilled /> <span style={{ color: blue[5] }}>Copy to Clipboard</span>
<Editor value={LogJsonData} language="json" height="70vh" readOnly /> </Button>
</div> </Row>
</div> <div style={{ marginTop: '0.5rem' }}>
); <Editor
value={LogJsonData}
language="json"
height="70vh"
readOnly
onChange={(): void => {}}
/>
</div>
</div>
);
} }
export default JSONView; export default JSONView;

View File

@ -1,47 +1,52 @@
import { blue, orange } from '@ant-design/colors'; import { blue, orange } from '@ant-design/colors';
import { import { Input, Table } from 'antd';
MenuFoldOutlined,
MinusCircleOutlined,
PlusCircleFilled,
PlusCircleOutlined,
} from '@ant-design/icons';
import { Button, Col, Input, Popover, Table, Typography } from 'antd';
import AddToQueryHOC from 'components/Logs/AddToQueryHOC'; import AddToQueryHOC from 'components/Logs/AddToQueryHOC';
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC'; import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
import flatten from 'flat'; import flatten from 'flat';
import { fieldSearchFilter } from 'lib/logs/fieldSearch'; import { fieldSearchFilter } from 'lib/logs/fieldSearch';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { ILog } from 'types/api/logs/log';
import ActionItem from './ActionItem'; import ActionItem from './ActionItem';
// Fields which should be restricted from adding it to query // Fields which should be restricted from adding it to query
const RESTRICTED_FIELDS = ['timestamp']; const RESTRICTED_FIELDS = ['timestamp'];
function TableView({ logData }) { interface TableViewProps {
logData: ILog;
}
function TableView({ logData }: TableViewProps): JSX.Element | null {
const [fieldSearchInput, setFieldSearchInput] = useState<string>(''); const [fieldSearchInput, setFieldSearchInput] = useState<string>('');
const flattenLogData = useMemo(() => (logData ? flatten(logData) : null), [ const flattenLogData: Record<string, never> | null = useMemo(
logData, () => (logData ? flatten(logData) : null),
]); [logData],
);
if (logData === null) { if (logData === null) {
return null; return null;
} }
const dataSource = Object.keys(flattenLogData) const dataSource =
.filter((field) => fieldSearchFilter(field, fieldSearchInput)) flattenLogData !== null &&
.map((key) => { Object.keys(flattenLogData)
return { .filter((field) => fieldSearchFilter(field, fieldSearchInput))
key, .map((key) => {
field: key, return {
value: JSON.stringify(flattenLogData[key]), key,
}; field: key,
}); value: JSON.stringify(flattenLogData[key]),
};
});
if (!dataSource) {
return null;
}
const columns = [ const columns = [
{ {
title: 'Action', title: 'Action',
width: 75, width: 75,
render: (fieldData) => { render: (fieldData: Record<string, string>): JSX.Element | null => {
const fieldKey = fieldData.field.split('.').slice(-1); const fieldKey = fieldData.field.split('.').slice(-1);
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) { if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
return <ActionItem fieldKey={fieldKey} fieldValue={fieldData.value} />; return <ActionItem fieldKey={fieldKey} fieldValue={fieldData.value} />;
@ -54,13 +59,13 @@ function TableView({ logData }) {
dataIndex: 'field', dataIndex: 'field',
key: 'field', key: 'field',
width: '35%', width: '35%',
render: (field: string) => { render: (field: string): JSX.Element => {
const fieldKey = field.split('.').slice(-1); const fieldKey = field.split('.').slice(-1);
const renderedField = <span style={{ color: blue[4] }}>{field}</span>; const renderedField = <span style={{ color: blue[4] }}>{field}</span>;
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) { if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
return ( return (
<AddToQueryHOC fieldKey={fieldKey} fieldValue={flattenLogData[field]}> <AddToQueryHOC fieldKey={fieldKey[0]} fieldValue={flattenLogData[field]}>
{' '} {' '}
{renderedField} {renderedField}
</AddToQueryHOC> </AddToQueryHOC>
@ -74,7 +79,7 @@ function TableView({ logData }) {
dataIndex: 'value', dataIndex: 'value',
key: 'value', key: 'value',
ellipsis: false, ellipsis: false,
render: (field) => ( render: (field: never): JSX.Element => (
<CopyClipboardHOC textToCopy={field}> <CopyClipboardHOC textToCopy={field}>
<span style={{ color: orange[6] }}>{field}</span> <span style={{ color: orange[6] }}>{field}</span>
</CopyClipboardHOC> </CopyClipboardHOC>
@ -88,13 +93,13 @@ function TableView({ logData }) {
placeholder="Search field names" placeholder="Search field names"
size="large" size="large"
value={fieldSearchInput} value={fieldSearchInput}
onChange={(e) => setFieldSearchInput(e.target.value)} onChange={(e): void => setFieldSearchInput(e.target.value)}
/> />
<Table <Table
// scroll={{ x: true }} // scroll={{ x: true }}
tableLayout="fixed" tableLayout="fixed"
dataSource={dataSource} dataSource={dataSource}
columns={columns} columns={columns as never}
pagination={false} pagination={false}
/> />
</div> </div>

View File

@ -3,49 +3,51 @@ import React from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs'; import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import JSONView from './JsonView'; import JSONView from './JsonView';
import TableView from './TableView'; import TableView from './TableView';
const { TabPane } = Tabs; const { TabPane } = Tabs;
function LogDetailedView() { function LogDetailedView(): JSX.Element {
const { detailedLog } = useSelector<AppState, ILogsReducer>( const { detailedLog } = useSelector<AppState, ILogsReducer>(
(state) => state.logs, (state) => state.logs,
); );
const dispatch = useDispatch(); const dispatch = useDispatch();
const onDrawerClose = () => { const onDrawerClose = (): void => {
dispatch({ dispatch({
type: SET_DETAILED_LOG_DATA, type: SET_DETAILED_LOG_DATA,
payload: null, payload: null,
}); });
}; };
return ( return (
<div style={{}}> <div style={{}}>
<Drawer <Drawer
width="60%" width="60%"
title="Log Details" title="Log Details"
placement="right" placement="right"
closable closable
mask={false} mask={false}
onClose={onDrawerClose} onClose={onDrawerClose}
visible={detailedLog !== null} visible={detailedLog !== null}
getContainer={false} getContainer={false}
style={{ overscrollBehavior: 'contain' }} style={{ overscrollBehavior: 'contain' }}
> >
<Tabs defaultActiveKey="1"> {detailedLog && (
<TabPane tab="Table" key="1"> <Tabs defaultActiveKey="1">
<TableView logData={detailedLog} /> <TabPane tab="Table" key="1">
</TabPane> <TableView logData={detailedLog} />
<TabPane tab="JSON" key="2"> </TabPane>
<JSONView logData={detailedLog} /> <TabPane tab="JSON" key="2">
</TabPane> <JSONView logData={detailedLog} />
</Tabs> </TabPane>
</Drawer> </Tabs>
</div> )}
); </Drawer>
</div>
);
} }
export default LogDetailedView; export default LogDetailedView;

View File

@ -1,23 +1,26 @@
import React from 'react'; import React from 'react';
function OptionIcon({ isDarkMode }) { interface OptionIconProps {
return ( isDarkMode: boolean;
<svg }
xmlns="http://www.w3.org/2000/svg" function OptionIcon({ isDarkMode }: OptionIconProps): JSX.Element {
x="0px" return (
y="0px" <svg
width="1rem" xmlns="http://www.w3.org/2000/svg"
height="1rem" x="0px"
viewBox="0 0 52 52" y="0px"
enableBackground="new 0 0 52 52" width="1rem"
fill={isDarkMode ? "#eee" : '#222'} height="1rem"
> viewBox="0 0 52 52"
<path enableBackground="new 0 0 52 52"
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 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" 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> </svg>
); );
} }
export default OptionIcon; export default OptionIcon;

View File

@ -1,27 +1,11 @@
import { green, red } from '@ant-design/colors'; /* eslint-disable react-hooks/exhaustive-deps */
import { import { green } from '@ant-design/colors';
ArrowRightOutlined, import { PauseOutlined, PlayCircleOutlined } from '@ant-design/icons';
IeSquareFilled, import { Button, Popover, Row, Select } from 'antd';
PauseOutlined,
PlayCircleOutlined,
PlaySquareFilled,
ReloadOutlined,
StopFilled,
} from '@ant-design/icons';
import { fetchEventSource } from '@microsoft/fetch-event-source';
import { Button, Card, Popover, Row, Select, Typography } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
import { LiveTail } from 'api/logs/livetail'; import { LiveTail } from 'api/logs/livetail';
import { LOCALSTORAGE } from 'constants/localStorage';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { debounce, throttle } from 'lodash-es'; import { throttle } from 'lodash-es';
import React, { import React, { useCallback, useEffect, useMemo, useRef } from 'react';
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { import {
@ -32,7 +16,7 @@ import {
} from 'types/actions/logs'; } from 'types/actions/logs';
import { TLogsLiveTailState } from 'types/api/logs/liveTail'; import { TLogsLiveTailState } from 'types/api/logs/liveTail';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import OptionIcon from './OptionIcon'; import OptionIcon from './OptionIcon';
import { TimePickerCard, TimePickerSelect } from './styles'; import { TimePickerCard, TimePickerSelect } from './styles';
@ -66,7 +50,7 @@ const TIME_PICKER_OPTIONS = [
}, },
]; ];
function LogLiveTail() { function LogLiveTail(): JSX.Element {
const { const {
liveTail, liveTail,
searchFilter: { queryString }, searchFilter: { queryString },
@ -75,15 +59,16 @@ function LogLiveTail() {
} = useSelector<AppState, ILogsReducer>((state) => state.logs); } = useSelector<AppState, ILogsReducer>((state) => state.logs);
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app); const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
const dispatch = useDispatch(); const dispatch = useDispatch();
const handleLiveTail = (toggleState: TLogsLiveTailState) => { const handleLiveTail = (toggleState: TLogsLiveTailState): void => {
dispatch({ dispatch({
type: TOGGLE_LIVE_TAIL, type: TOGGLE_LIVE_TAIL,
payload: toggleState, payload: toggleState,
}); });
}; };
const batchedEventsRef = useRef([]); const batchedEventsRef = useRef<Record<string, unknown>[]>([]);
// eslint-disable-next-line react-hooks/exhaustive-deps
const pushLiveLog = useCallback( const pushLiveLog = useCallback(
throttle(() => { throttle(() => {
dispatch({ dispatch({
@ -96,46 +81,42 @@ function LogLiveTail() {
[], [],
); );
const batchLiveLog = (e) => { const batchLiveLog = (e: { data: string }): void => {
// console.log('EVENT BATCHED'); // console.log('EVENT BATCHED');
batchedEventsRef.current.push(JSON.parse(e.data)); batchedEventsRef.current.push(JSON.parse(e.data as string) as never);
pushLiveLog(); pushLiveLog();
}; };
// This ref depicts thats whether the live tail is played from paused state or not. // This ref depicts thats whether the live tail is played from paused state or not.
const liveTailSourceRef = useRef(null); const liveTailSourceRef = useRef<EventSource | null>(null);
useEffect(() => { useEffect(() => {
if (liveTail === 'PLAYING') { if (liveTail === 'PLAYING') {
// console.log('Starting Live Tail', logs.length); // console.log('Starting Live Tail', logs.length);
const timeStamp = dayjs().subtract(liveTailStartRange, 'minute').valueOf(); const timeStamp = dayjs().subtract(liveTailStartRange, 'minute').valueOf();
const queryParams = new URLSearchParams({ const queryParams = new URLSearchParams({
...(queryString ? { q: queryString } : {}), ...(queryString ? { q: queryString } : {}),
timestampStart: timeStamp * 1e6, timestampStart: (timeStamp * 1e6) as never,
...(liveTailSourceRef.current && logs.length > 0 ...(liveTailSourceRef.current && logs.length > 0
? { ? {
idGt: logs[0].id, idGt: logs[0].id,
} }
: {}), : {}),
}); });
const source = LiveTail(queryParams.toString()); const source = LiveTail(queryParams.toString());
liveTailSourceRef.current = source; liveTailSourceRef.current = source;
source.onmessage = function (e) { source.onmessage = function connectionMessage(e): void {
// pushLiveLog(e)
batchLiveLog(e); batchLiveLog(e);
}; };
source.onopen = function (event) { // source.onopen = function connectionOpen(): void { };
// console.log('open event'); source.onerror = function connectionError(event: unknown): void {
// console.log(event); console.error(event);
};
source.onerror = function (event) {
// console.log(event);
source.close(); source.close();
dispatch({ dispatch({
type: TOGGLE_LIVE_TAIL, type: TOGGLE_LIVE_TAIL,
payload: false, payload: false,
}); });
}; };
} else if (liveTailSourceRef.current) { } else if (liveTailSourceRef.current && liveTailSourceRef.current.close) {
liveTailSourceRef.current?.close(); liveTailSourceRef.current?.close();
} }
@ -144,7 +125,7 @@ function LogLiveTail() {
} }
}, [liveTail]); }, [liveTail]);
const handleLiveTailStart = () => { const handleLiveTailStart = (): void => {
handleLiveTail('PLAYING'); handleLiveTail('PLAYING');
if (!liveTailSourceRef.current) { if (!liveTailSourceRef.current) {
dispatch({ dispatch({
@ -158,7 +139,7 @@ function LogLiveTail() {
<TimePickerSelect <TimePickerSelect
disabled={liveTail === 'PLAYING'} disabled={liveTail === 'PLAYING'}
value={liveTailStartRange} value={liveTailStartRange}
onChange={(value) => { onChange={(value): void => {
dispatch({ dispatch({
type: SET_LIVE_TAIL_START_TIME, type: SET_LIVE_TAIL_START_TIME,
payload: value, payload: value,
@ -183,7 +164,7 @@ function LogLiveTail() {
{liveTail === 'PLAYING' ? ( {liveTail === 'PLAYING' ? (
<Button <Button
type="primary" type="primary"
onClick={() => handleLiveTail('PAUSED')} onClick={(): void => handleLiveTail('PAUSED')}
title="Pause live tail" title="Pause live tail"
style={{ background: green[6] }} style={{ background: green[6] }}
> >
@ -201,7 +182,7 @@ function LogLiveTail() {
{liveTail !== 'STOPPED' && ( {liveTail !== 'STOPPED' && (
<Button <Button
type="dashed" type="dashed"
onClick={() => handleLiveTail('STOPPED')} onClick={(): void => handleLiveTail('STOPPED')}
title="Exit live tail" title="Exit live tail"
> >
<div <div

View File

@ -15,7 +15,10 @@ import { GetLogsFields } from 'store/actions/logs/getFields';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs'; import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs';
function Logs({ getLogsFields }) { interface LogsProps {
getLogsFields: VoidFunction;
}
function Logs({ getLogsFields }: LogsProps): JSX.Element {
const { search } = useLocation(); const { search } = useLocation();
const urlQuery = useMemo(() => { const urlQuery = useMemo(() => {
@ -29,7 +32,7 @@ function Logs({ getLogsFields }) {
type: SET_SEARCH_QUERY_STRING, type: SET_SEARCH_QUERY_STRING,
payload: urlQuery.get('q'), payload: urlQuery.get('q'),
}); });
}, [dispatch]); }, [dispatch, urlQuery]);
useEffect(() => { useEffect(() => {
getLogsFields(); getLogsFields();
@ -39,16 +42,16 @@ function Logs({ getLogsFields }) {
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
<Row style={{ justifyContent: 'center', alignItems: 'center' }}> <Row style={{ justifyContent: 'center', alignItems: 'center' }}>
<SearchFilter /> <SearchFilter />
<Divider type='vertical' style={{ height: '2rem' }} /> <Divider type="vertical" style={{ height: '2rem' }} />
<LogLiveTail /> <LogLiveTail />
</Row> </Row>
<LogsAggregate /> <LogsAggregate />
<LogControls /> <LogControls />
<Divider style={{ margin: 0 }} /> <Divider style={{ margin: 0 }} />
<Row gutter={20} style={{ flexWrap: 'nowrap' }}> <Row gutter={20} style={{ flexWrap: 'nowrap' }}>
<LogsFilters flex="450px" /> <LogsFilters />
<Divider type="vertical" style={{ height: '100%', margin: 0 }} /> <Divider type="vertical" style={{ height: '100%', margin: 0 }} />
<LogsTable flex="auto" /> <LogsTable />
</Row> </Row>
<LogDetailedView /> <LogDetailedView />
</div> </div>

View File

@ -1,42 +1,41 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { blue } from '@ant-design/colors'; import { blue } from '@ant-design/colors';
import Graph from 'components/Graph'; import Graph from 'components/Graph';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import getStep from 'lib/getStep'; import getStep from 'lib/getStep';
import React, { memo, useEffect, useRef } from 'react'; import React, { memo, useEffect, useRef } from 'react';
import { connect, useDispatch, useSelector } from 'react-redux'; import { connect, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux'; import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate'; import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import { Container } from './styles'; import { Container } from './styles';
function LogsAggregate({ getLogsAggregate }) { interface LogsAggregateProps {
getLogsAggregate: (arg0: Parameters<typeof getLogsAggregate>[0]) => void;
}
function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element {
const { const {
searchFilter: { queryString }, searchFilter: { queryString },
logs,
logLinesPerPage,
idEnd, idEnd,
idStart, idStart,
isLoading,
isLoadingAggregate, isLoadingAggregate,
logsAggregate, logsAggregate,
liveTail, liveTail,
liveTailStartRange, liveTailStartRange,
} = useSelector<AppState, ILogsReducer>((state) => state.logs); } = useSelector<AppState, ILogsReducer>((state) => state.logs);
const dispatch = useDispatch();
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
); );
const reFetchIntervalRef = useRef(null); const reFetchIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => { useEffect(() => {
// console.log('LIVE TAIL LOG AGG', liveTail)
switch (liveTail) { switch (liveTail) {
case 'STOPPED': { case 'STOPPED': {
if (reFetchIntervalRef.current) { if (reFetchIntervalRef.current) {
@ -59,7 +58,7 @@ function LogsAggregate({ getLogsAggregate }) {
} }
case 'PLAYING': { case 'PLAYING': {
const aggregateCall = () => { const aggregateCall = (): void => {
const startTime = const startTime =
dayjs().subtract(liveTailStartRange, 'minute').valueOf() * 1e6; dayjs().subtract(liveTailStartRange, 'minute').valueOf() * 1e6;
const endTime = dayjs().valueOf() * 1e6; const endTime = dayjs().valueOf() * 1e6;
@ -72,17 +71,15 @@ function LogsAggregate({ getLogsAggregate }) {
inputFormat: 'ns', inputFormat: 'ns',
}), }),
q: queryString, q: queryString,
...(idStart ? {idGt: idStart } : {}), ...(idStart ? { idGt: idStart } : {}),
...(idEnd ? { idLt: idEnd } : {}), ...(idEnd ? { idLt: idEnd } : {}),
}); });
}; };
aggregateCall(); aggregateCall();
reFetchIntervalRef.current = setInterval(aggregateCall, 60000); reFetchIntervalRef.current = setInterval(aggregateCall, 60000);
// console.log('LA Play', reFetchIntervalRef.current);
break; break;
} }
case 'PAUSED': { case 'PAUSED': {
// console.log('LA Pause', reFetchIntervalRef.current);
if (reFetchIntervalRef.current) { if (reFetchIntervalRef.current) {
clearInterval(reFetchIntervalRef.current); clearInterval(reFetchIntervalRef.current);
} }
@ -98,7 +95,6 @@ function LogsAggregate({ getLogsAggregate }) {
labels: logsAggregate.map((s) => new Date(s.timestamp / 1000000)), labels: logsAggregate.map((s) => new Date(s.timestamp / 1000000)),
datasets: [ datasets: [
{ {
// label: 'Span Count',
data: logsAggregate.map((s) => s.value), data: logsAggregate.map((s) => s.value),
backgroundColor: blue[4], backgroundColor: blue[4],
}, },
@ -123,7 +119,9 @@ function LogsAggregate({ getLogsAggregate }) {
} }
interface DispatchProps { interface DispatchProps {
getLogsAggregate: () => (dispatch: Dispatch<AppActions>) => void; getLogsAggregate: (
props: Parameters<typeof getLogsAggregate>[0],
) => (dispatch: Dispatch<AppActions>) => void;
} }
const mapDispatchToProps = ( const mapDispatchToProps = (

View File

@ -1,18 +1,21 @@
import { import { LoadingOutlined } from '@ant-design/icons';
CloseCircleFilled,
CloseOutlined,
LoadingOutlined,
} from '@ant-design/icons';
import { Button, Popover, Spin } from 'antd'; import { Button, Popover, Spin } from 'antd';
import Spinner from 'components/Spinner'; import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { useHover, useHoverDirty } from 'react-use';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
import { Field } from './styles'; import { Field } from './styles';
interface FieldItemProps {
name: string;
buttonIcon: React.ReactNode;
buttonOnClick: (arg0: Record<string, unknown>) => void;
fieldData: Record<string, never>;
fieldIndex: number;
isLoading: boolean;
iconHoverText: string;
}
export function FieldItem({ export function FieldItem({
name, name,
buttonIcon, buttonIcon,
@ -20,16 +23,16 @@ export function FieldItem({
fieldData, fieldData,
fieldIndex, fieldIndex,
isLoading, isLoading,
iconHoverText iconHoverText,
}) { }: FieldItemProps): JSX.Element {
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app); const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
return ( return (
<Field <Field
onMouseEnter={() => { onMouseEnter={(): void => {
setIsHovered(true); setIsHovered(true);
}} }}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={(): void => setIsHovered(false)}
isDarkMode={isDarkMode} isDarkMode={isDarkMode}
> >
<span>{name}</span> <span>{name}</span>
@ -43,7 +46,7 @@ export function FieldItem({
type="text" type="text"
size="small" size="small"
icon={buttonIcon} icon={buttonIcon}
onClick={() => buttonOnClick({ fieldData, fieldIndex })} onClick={(): void => buttonOnClick({ fieldData, fieldIndex })}
style={{ color: 'inherit', padding: 0, height: '1rem', width: '1rem' }} style={{ color: 'inherit', padding: 0, height: '1rem', width: '1rem' }}
/> />
</Popover> </Popover>

View File

@ -1,10 +1,6 @@
/* eslint-disable react/no-array-index-key */
import { red } from '@ant-design/colors'; import { red } from '@ant-design/colors';
import { import { CloseOutlined, PlusCircleFilled } from '@ant-design/icons';
CloseCircleFilled,
CloseOutlined,
PlusCircleFilled,
PlusCircleOutlined,
} from '@ant-design/icons';
import { Input } from 'antd'; import { Input } from 'antd';
import AddToSelectedFields from 'api/logs/AddToSelectedField'; import AddToSelectedFields from 'api/logs/AddToSelectedField';
import RemoveSelectedField from 'api/logs/RemoveFromSelectedField'; import RemoveSelectedField from 'api/logs/RemoveFromSelectedField';
@ -17,34 +13,40 @@ import { ThunkDispatch } from 'redux-thunk';
import { GetLogsFields } from 'store/actions/logs/getFields'; import { GetLogsFields } from 'store/actions/logs/getFields';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import ILogsReducer from 'types/reducer/logs'; import { IInterestingFields, ISelectedFields } from 'types/api/logs/fields';
import { ILogsReducer } from 'types/reducer/logs';
import { FieldItem } from './FieldItem'; import { FieldItem } from './FieldItem';
import { import { CategoryContainer, Container, FieldContainer } from './styles';
CategoryContainer,
Container,
ExtractField,
Field,
FieldContainer,
} from './styles';
const RESTRICTED_SELECTED_FIELDS = ['timestamp', 'id']; const RESTRICTED_SELECTED_FIELDS = ['timestamp', 'id'];
function LogsFilters({ getLogsFields }) { interface LogsFiltersProps {
getLogsFields: () => void;
}
function LogsFilters({ getLogsFields }: LogsFiltersProps): JSX.Element {
const { const {
fields: { interesting, selected }, fields: { interesting, selected },
} = useSelector<AppState, ILogsReducer>((state) => state.logs); } = useSelector<AppState, ILogsReducer>((state) => state.logs);
const [selectedFieldLoading, setSelectedFieldLoading] = useState([]); const [selectedFieldLoading, setSelectedFieldLoading] = useState<number[]>([]);
const [interestingFieldLoading, setInterestingFieldLoading] = useState([]); const [interestingFieldLoading, setInterestingFieldLoading] = useState<
number[]
>([]);
const [filterValuesInput, setFilterValuesInput] = useState(''); const [filterValuesInput, setFilterValuesInput] = useState('');
const handleSearch = (e) => { const handleSearch = (e: React.ChangeEvent<HTMLInputElement>): void => {
setFilterValuesInput(e.target.value); setFilterValuesInput((e.target as HTMLInputElement).value);
}; };
const handleAddInterestingToSelected = async ({ fieldData, fieldIndex }) => { const handleAddInterestingToSelected = async ({
setInterestingFieldLoading((prevState) => { fieldData,
fieldIndex,
}: {
fieldData: IInterestingFields;
fieldIndex: number;
}): Promise<void> => {
setInterestingFieldLoading((prevState: number[]) => {
prevState.push(fieldIndex); prevState.push(fieldIndex);
return [...prevState]; return [...prevState];
}); });
@ -59,7 +61,13 @@ function LogsFilters({ getLogsFields }) {
interestingFieldLoading.filter((e) => e !== fieldIndex), interestingFieldLoading.filter((e) => e !== fieldIndex),
); );
}; };
const handleRemoveSelectedField = async ({ fieldData, fieldIndex }) => { const handleRemoveSelectedField = async ({
fieldData,
fieldIndex,
}: {
fieldData: ISelectedFields;
fieldIndex: number;
}): Promise<void> => {
setSelectedFieldLoading((prevState) => { setSelectedFieldLoading((prevState) => {
prevState.push(fieldIndex); prevState.push(fieldIndex);
return [...prevState]; return [...prevState];
@ -77,7 +85,7 @@ function LogsFilters({ getLogsFields }) {
); );
}; };
return ( return (
<Container> <Container flex="450px">
<Input <Input
placeholder="Filter Values" placeholder="Filter Values"
onInput={handleSearch} onInput={handleSearch}
@ -93,14 +101,14 @@ function LogsFilters({ getLogsFields }) {
.filter((field) => fieldSearchFilter(field.name, filterValuesInput)) .filter((field) => fieldSearchFilter(field.name, filterValuesInput))
.map((field, idx) => ( .map((field, idx) => (
<FieldItem <FieldItem
key={field + idx} key={`${JSON.stringify(field)}-${idx}`}
name={field.name} name={field.name}
fieldData={field} fieldData={field as never}
fieldIndex={idx} fieldIndex={idx}
buttonIcon={<CloseOutlined style={{ color: red[5] }} />} buttonIcon={<CloseOutlined style={{ color: red[5] }} />}
buttonOnClick={ buttonOnClick={
!RESTRICTED_SELECTED_FIELDS.includes(field.name) && (!RESTRICTED_SELECTED_FIELDS.includes(field.name) &&
handleRemoveSelectedField handleRemoveSelectedField) as never
} }
isLoading={selectedFieldLoading.includes(idx)} isLoading={selectedFieldLoading.includes(idx)}
iconHoverText="Remove from Selected Fields" iconHoverText="Remove from Selected Fields"
@ -115,12 +123,12 @@ function LogsFilters({ getLogsFields }) {
.filter((field) => fieldSearchFilter(field.name, filterValuesInput)) .filter((field) => fieldSearchFilter(field.name, filterValuesInput))
.map((field, idx) => ( .map((field, idx) => (
<FieldItem <FieldItem
key={field + idx} key={`${JSON.stringify(field)}-${idx}`}
name={field.name} name={field.name}
fieldData={field} fieldData={field as never}
fieldIndex={idx} fieldIndex={idx}
buttonIcon={<PlusCircleFilled />} buttonIcon={<PlusCircleFilled />}
buttonOnClick={handleAddInterestingToSelected} buttonOnClick={handleAddInterestingToSelected as never}
isLoading={interestingFieldLoading.includes(idx)} isLoading={interestingFieldLoading.includes(idx)}
iconHoverText="Add to Selected Fields" iconHoverText="Add to Selected Fields"
/> />

View File

@ -1,15 +1,20 @@
import { Typography } from 'antd'; import { Typography } from 'antd';
import React from 'react'; import React from 'react';
function FieldKey({ name, type }) { interface FieldKeyProps {
return ( name: string;
<span style={{ margin: '0.25rem 0', display: 'flex', gap: '0.5rem' }}> type: string;
<Typography.Text>{name}</Typography.Text> }
<Typography.Text type="secondary" italic>
{type} function FieldKey({ name, type }: FieldKeyProps): JSX.Element {
</Typography.Text> return (
</span> <span style={{ margin: '0.25rem 0', display: 'flex', gap: '0.5rem' }}>
); <Typography.Text>{name}</Typography.Text>
<Typography.Text type="secondary" italic>
{type}
</Typography.Text>
</span>
);
} }
export default FieldKey; export default FieldKey;

View File

@ -1,24 +1,22 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-bitwise */
/* eslint-disable sonarjs/no-identical-functions */
/* 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 } from '@ant-design/icons';
import { Button, Input, Select, Typography } from 'antd'; import { Button, Input, Select } from 'antd';
import CategoryHeading from 'components/Logs/CategoryHeading'; import CategoryHeading from 'components/Logs/CategoryHeading';
import { import {
ConditionalOperators, ConditionalOperators,
QueryOperatorsMultiVal, QueryOperatorsMultiVal,
QueryOperatorsSingleVal, QueryOperatorsSingleVal,
QueryTypes,
} from 'lib/logql/tokens'; } from 'lib/logql/tokens';
import { chunk, cloneDeep, debounce, flatten } from 'lodash-es'; import { flatten } from 'lodash-es';
import React, { import React, { useEffect, useMemo, useRef, useState } from 'react';
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { useHoverDirty, useLocation } from 'react-use';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import FieldKey from '../FieldKey'; import FieldKey from '../FieldKey';
@ -26,12 +24,24 @@ import { QueryConditionContainer, QueryFieldContainer } from '../styles';
import { createParsedQueryStructure } from '../utils'; import { createParsedQueryStructure } from '../utils';
const { Option } = Select; const { Option } = Select;
function QueryField({ query, queryIndex, onUpdate, onDelete }) { interface QueryFieldProps {
query: { value: string | string[]; type: string }[];
queryIndex: number;
onUpdate: (query: unknown, queryIndex: number) => void;
onDelete: (queryIndex: number) => void;
}
function QueryField({
query,
queryIndex,
onUpdate,
onDelete,
}: QueryFieldProps): JSX.Element | null {
const { const {
fields: { selected }, fields: { selected },
} = useSelector<AppState, ILogsReducer>((store) => store.logs); } = useSelector<AppState, ILogsReducer>((store) => store.logs);
const getFieldType = (inputKey) => { const getFieldType = (inputKey: string): string => {
// eslint-disable-next-line no-restricted-syntax
for (const selectedField of selected) { for (const selectedField of selected) {
if (inputKey === selectedField.name) { if (inputKey === selectedField.name) {
return selectedField.type; return selectedField.type;
@ -39,11 +49,10 @@ function QueryField({ query, queryIndex, onUpdate, onDelete }) {
} }
return ''; return '';
}; };
const fieldType = useMemo(() => getFieldType(query[0].value), [ const fieldType = useMemo(() => getFieldType(query[0].value as string), [
query, query,
selected,
]); ]);
const handleChange = (qIdx, value) => { const handleChange = (qIdx: number, value: string): void => {
query[qIdx].value = value || ''; query[qIdx].value = value || '';
if (qIdx === 1) { if (qIdx === 1) {
@ -51,16 +60,17 @@ function QueryField({ query, queryIndex, onUpdate, onDelete }) {
if (!Array.isArray(query[2].value)) { if (!Array.isArray(query[2].value)) {
query[2].value = []; query[2].value = [];
} }
} else if (Object.values(QueryOperatorsSingleVal).includes(value)) { } else if (
if (Array.isArray(query[2].value)) { Object.values(QueryOperatorsSingleVal).includes(value) &&
query[2].value = ''; Array.isArray(query[2].value)
} ) {
query[2].value = '';
} }
} }
onUpdate(query, queryIndex); onUpdate(query, queryIndex);
}; };
const handleClear = () => { const handleClear = (): void => {
onDelete(queryIndex); onDelete(queryIndex);
}; };
if (!Array.isArray(query)) { if (!Array.isArray(query)) {
@ -72,39 +82,43 @@ function QueryField({ query, queryIndex, onUpdate, onDelete }) {
style={{ ...(queryIndex === 0 && { gridColumnStart: 2 }) }} style={{ ...(queryIndex === 0 && { gridColumnStart: 2 }) }}
> >
<div style={{ flex: 1, minWidth: 100 }}> <div style={{ flex: 1, minWidth: 100 }}>
<FieldKey name={query[0] && query[0].value} type={fieldType} /> <FieldKey name={(query[0] && query[0].value) as string} type={fieldType} />
</div> </div>
<Select <Select
defaultActiveFirstOption={false} defaultActiveFirstOption={false}
placeholder="Select Operator" placeholder="Select Operator"
defaultValue={ defaultValue={
query[1] && query[1].value ? query[1].value.toUpperCase() : null query[1] && query[1].value
? (query[1].value as string).toUpperCase()
: null
} }
onChange={(e) => handleChange(1, e)} onChange={(e): void => handleChange(1, e)}
style={{ minWidth: 150 }} style={{ minWidth: 150 }}
> >
{Object.values({ {Object.values({
...QueryOperatorsMultiVal, ...QueryOperatorsMultiVal,
...QueryOperatorsSingleVal, ...QueryOperatorsSingleVal,
}).map((cond) => ( }).map((cond) => (
<Option key={cond} value={cond} label={cond} /> <Option key={cond} value={cond} label={cond}>
{cond}
</Option>
))} ))}
</Select> </Select>
<div style={{ flex: 2 }}> <div style={{ flex: 2 }}>
{Array.isArray(query[2].value) || {Array.isArray(query[2].value) ||
Object.values(QueryOperatorsMultiVal).some( Object.values(QueryOperatorsMultiVal).some(
(op) => op.toUpperCase() === query[1].value?.toUpperCase(), (op) => op.toUpperCase() === (query[1].value as string)?.toUpperCase(),
) ? ( ) ? (
<Select <Select
mode="tags" mode="tags"
style={{ width: '100%' }} style={{ width: '100%' }}
onChange={(e) => handleChange(2, e)} onChange={(e): void => handleChange(2, e as never)}
defaultValue={(query[2] && query[2].value) || []} defaultValue={(query[2] && query[2].value) || []}
notFoundContent={null} notFoundContent={null}
/> />
) : ( ) : (
<Input <Input
onChange={(e) => handleChange(2, e.target.value)} onChange={(e): void => handleChange(2, e.target.value)}
style={{ width: '100%' }} style={{ width: '100%' }}
defaultValue={query[2] && query[2].value} defaultValue={query[2] && query[2].value}
/> />
@ -120,24 +134,39 @@ function QueryField({ query, queryIndex, onUpdate, onDelete }) {
</QueryFieldContainer> </QueryFieldContainer>
); );
} }
function QueryConditionField({ query, queryIndex, onUpdate }) {
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 ( return (
<QueryConditionContainer> <QueryConditionContainer>
<Select <Select
defaultValue={query.value.toUpperCase()} defaultValue={
onChange={(e) => { (query as any).value &&
(((query as any)?.value as any) as string).toUpperCase()
}
onChange={(e): void => {
onUpdate({ ...query, value: e }, queryIndex); onUpdate({ ...query, value: e }, queryIndex);
}} }}
style={{ width: '100%' }} style={{ width: '100%' }}
> >
{Object.values(ConditionalOperators).map((cond) => ( {Object.values(ConditionalOperators).map((cond) => (
<Option key={cond} value={cond} label={cond} /> <Option key={cond} value={cond} label={cond}>
{cond}
</Option>
))} ))}
</Select> </Select>
</QueryConditionContainer> </QueryConditionContainer>
); );
} }
const hashCode = (s) => { const hashCode = (s: string): string => {
if (!s) { if (!s) {
return '0'; return '0';
} }
@ -149,14 +178,20 @@ const hashCode = (s) => {
)}`; )}`;
}; };
function QueryBuilder({ updateParsedQuery }) { function QueryBuilder({
updateParsedQuery,
}: {
updateParsedQuery: (arg0: unknown) => void;
}): JSX.Element {
const { const {
searchFilter: { parsedQuery }, searchFilter: { parsedQuery },
} = useSelector<AppState, ILogsReducer>((store) => store.logs); } = useSelector<AppState, ILogsReducer>((store) => store.logs);
const keyPrefixRef = useRef(hashCode(JSON.stringify(parsedQuery))); const keyPrefixRef = useRef(hashCode(JSON.stringify(parsedQuery)));
const [keyPrefix, setKeyPrefix] = useState(keyPrefixRef.current); const [keyPrefix, setKeyPrefix] = useState(keyPrefixRef.current);
const generatedQueryStructure = createParsedQueryStructure(parsedQuery); const generatedQueryStructure = createParsedQueryStructure(
parsedQuery as never[],
);
useEffect(() => { useEffect(() => {
const incomingHashCode = hashCode(JSON.stringify(parsedQuery)); const incomingHashCode = hashCode(JSON.stringify(parsedQuery));
@ -166,18 +201,19 @@ function QueryBuilder({ updateParsedQuery }) {
} }
}, [parsedQuery]); }, [parsedQuery]);
const handleUpdate = (
query: { value: string | string[]; type: string }[],
const handleUpdate = (query, queryIndex): void => { queryIndex: number,
): void => {
const updatedParsedQuery = generatedQueryStructure; const updatedParsedQuery = generatedQueryStructure;
updatedParsedQuery[queryIndex] = query; updatedParsedQuery[queryIndex] = query as never;
const flatParsedQuery = flatten(updatedParsedQuery).filter((q) => q.value); const flatParsedQuery = flatten(updatedParsedQuery).filter((q) => q.value);
keyPrefixRef.current = hashCode(JSON.stringify(flatParsedQuery)); keyPrefixRef.current = hashCode(JSON.stringify(flatParsedQuery));
updateParsedQuery(flatParsedQuery); updateParsedQuery(flatParsedQuery);
}; };
const handleDelete = (queryIndex) => { const handleDelete = (queryIndex: number): void => {
const updatedParsedQuery = generatedQueryStructure; const updatedParsedQuery = generatedQueryStructure;
updatedParsedQuery.splice(queryIndex - 1, 2); updatedParsedQuery.splice(queryIndex - 1, 2);
@ -186,15 +222,15 @@ function QueryBuilder({ updateParsedQuery }) {
updateParsedQuery(flatParsedQuery); updateParsedQuery(flatParsedQuery);
}; };
const QueryUI = () => const QueryUI = (): JSX.Element | JSX.Element[] =>
generatedQueryStructure.map((query, idx) => { generatedQueryStructure.map((query, idx) => {
if (Array.isArray(query)) if (Array.isArray(query))
return ( return (
<QueryField <QueryField
key={keyPrefix + idx} key={keyPrefix + idx}
query={query} query={query as never}
queryIndex={idx} queryIndex={idx}
onUpdate={handleUpdate} onUpdate={handleUpdate as never}
onDelete={handleDelete} onDelete={handleDelete}
/> />
); );
@ -204,7 +240,7 @@ function QueryBuilder({ updateParsedQuery }) {
key={keyPrefix + idx} key={keyPrefix + idx}
query={query} query={query}
queryIndex={idx} queryIndex={idx}
onUpdate={handleUpdate} onUpdate={handleUpdate as never}
/> />
); );
}); });

View File

@ -1,18 +1,22 @@
import { Button, Typography } from 'antd'; import { Button } from 'antd';
import CategoryHeading from 'components/Logs/CategoryHeading'; import CategoryHeading from 'components/Logs/CategoryHeading';
import { map } from 'lodash-es'; import { map } from 'lodash-es';
import React from 'react'; import React from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { ADD_SEARCH_FIELD_QUERY_STRING } from 'types/actions/logs'; import { ADD_SEARCH_FIELD_QUERY_STRING } from 'types/actions/logs';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import FieldKey from './FieldKey'; import FieldKey from './FieldKey';
function SuggestedItem({ name, type }) { interface SuggestedItemProps {
name: string;
type: string;
}
function SuggestedItem({ name, type }: SuggestedItemProps): JSX.Element {
const dispatch = useDispatch(); const dispatch = useDispatch();
const addSuggestedField = () => { const addSuggestedField = (): void => {
dispatch({ dispatch({
type: ADD_SEARCH_FIELD_QUERY_STRING, type: ADD_SEARCH_FIELD_QUERY_STRING,
payload: name, payload: name,
@ -29,7 +33,7 @@ function SuggestedItem({ name, type }) {
); );
} }
function Suggestions() { function Suggestions(): JSX.Element {
const { const {
fields: { selected }, fields: { selected },
} = useSelector<AppState, ILogsReducer>((store) => store.logs); } = useSelector<AppState, ILogsReducer>((store) => store.logs);

View File

@ -1,9 +1,12 @@
import React from 'react'; import React from 'react';
import QueryBuilder from './QueryBuilder/QueryBuilder'; import QueryBuilder from './QueryBuilder/QueryBuilder';
import Suggestions from './Suggestions'; import Suggestions from './Suggestions';
function SearchFields({updateParsedQuery}): JSX.Element { interface SearchFieldsProps {
updateParsedQuery: () => void;
}
function SearchFields({ updateParsedQuery }: SearchFieldsProps): JSX.Element {
return ( return (
<> <>
<QueryBuilder updateParsedQuery={updateParsedQuery} /> <QueryBuilder updateParsedQuery={updateParsedQuery} />

View File

@ -1,3 +1,7 @@
/* eslint-disable */
// @ts-ignore
// @ts-nocheck
import { QueryTypes } from 'lib/logql/tokens'; import { QueryTypes } from 'lib/logql/tokens';
export const queryKOVPair = () => [ export const queryKOVPair = () => [
@ -30,7 +34,7 @@ export const createParsedQueryStructure = (parsedQuery = []) => {
cond = null; cond = null;
qCtr = -1; qCtr = -1;
} }
let stagingArr = structuredArray[structuredArray.length - 1]; const stagingArr = structuredArray[structuredArray.length - 1];
const prevQuery = const prevQuery =
Array.isArray(stagingArr) && qCtr >= 0 ? stagingArr[qCtr] : null; Array.isArray(stagingArr) && qCtr >= 0 ? stagingArr[qCtr] : null;

View File

@ -1,23 +1,11 @@
import { /* eslint-disable react-hooks/exhaustive-deps */
CloseCircleFilled, import { CloseSquareOutlined } from '@ant-design/icons';
CloseCircleOutlined,
CloseSquareOutlined,
} from '@ant-design/icons';
import { Button, Input } from 'antd'; import { Button, Input } from 'antd';
import useClickOutside from 'hooks/useClickOutside'; import useClickOutside from 'hooks/useClickOutside';
import getStep from 'lib/getStep'; import getStep from 'lib/getStep';
import { debounce, throttle } from 'lodash-es'; import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
import React, {
memo,
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from 'react';
import { connect, useDispatch, useSelector } from 'react-redux'; import { connect, useDispatch, useSelector } from 'react-redux';
import { useClickAway, useLocation } from 'react-use'; import { useLocation } from 'react-use';
import { bindActionCreators, Dispatch } from 'redux'; import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
import { getLogs } from 'store/actions/logs/getLogs'; import { getLogs } from 'store/actions/logs/getLogs';
@ -26,7 +14,7 @@ import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { TOGGLE_LIVE_TAIL } from 'types/actions/logs'; import { TOGGLE_LIVE_TAIL } from 'types/actions/logs';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import SearchFields from './SearchFields'; import SearchFields from './SearchFields';
import { DropDownContainer } from './styles'; import { DropDownContainer } from './styles';
@ -34,7 +22,16 @@ import { useSearchParser } from './useSearchParser';
const { Search } = Input; const { Search } = Input;
function SearchFilter({ getLogs, getLogsAggregate }) { interface SearchFilterProps {
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
getLogsAggregate: (
props: Parameters<typeof getLogsAggregate>[0],
) => ReturnType<typeof getLogsAggregate>;
}
function SearchFilter({
getLogs,
getLogsAggregate,
}: SearchFilterProps): JSX.Element {
const { const {
queryString, queryString,
updateParsedQuery, updateParsedQuery,
@ -146,26 +143,29 @@ function SearchFilter({ getLogs, getLogsAggregate }) {
<DropDownContainer> <DropDownContainer>
<Button <Button
type="text" type="text"
onClick={() => setShowDropDown(false)} onClick={(): void => setShowDropDown(false)}
style={{ style={{
position: 'absolute', position: 'absolute',
top: 0, top: 0,
right: 0, right: 0,
}} }}
> >
<CloseSquareOutlined size="large" /> <CloseSquareOutlined />
</Button> </Button>
<SearchFields updateParsedQuery={updateParsedQuery} /> <SearchFields updateParsedQuery={updateParsedQuery as never} />
</DropDownContainer> </DropDownContainer>
)} )}
</div> </div>
</div> </div>
); );
} }
interface DispatchProps { interface DispatchProps {
getLogs: () => (dispatch: Dispatch<AppActions>) => void; getLogs: (
getLogsAggregate: () => (dispatch: Dispatch<AppActions>) => void; props: Parameters<typeof getLogs>[0],
) => (dispatch: Dispatch<AppActions>) => void;
getLogsAggregate: (
props: Parameters<typeof getLogsAggregate>[0],
) => (dispatch: Dispatch<AppActions>) => void;
} }
const mapDispatchToProps = ( const mapDispatchToProps = (

View File

@ -1,16 +1,21 @@
import history from 'lib/history'; import history from 'lib/history';
import { parseQuery, reverseParser } from 'lib/logql'; import { parseQuery, reverseParser } from 'lib/logql';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { useCallback, useEffect, useReducer, useState } from 'react'; import { useCallback, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { import {
SET_SEARCH_QUERY_PARSED_PAYLOAD, SET_SEARCH_QUERY_PARSED_PAYLOAD,
SET_SEARCH_QUERY_STRING, SET_SEARCH_QUERY_STRING,
} from 'types/actions/logs'; } from 'types/actions/logs';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
export function useSearchParser() { export function useSearchParser(): {
queryString: string;
parsedQuery: unknown;
updateParsedQuery: (arg0: unknown) => void;
updateQueryString: (arg0: unknown) => void;
} {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { const {
searchFilter: { parsedQuery, queryString }, searchFilter: { parsedQuery, queryString },

View File

@ -1,23 +1,24 @@
import { fetchEventSource } from '@microsoft/fetch-event-source'; /* eslint-disable no-nested-ternary */
import { Card, Typography } from 'antd'; import { Typography } from 'antd';
import { LiveTail } from 'api/logs/livetail';
import LogItem from 'components/Logs/LogItem'; import LogItem from 'components/Logs/LogItem';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import { map } from 'lodash-es'; import { map } from 'lodash-es';
import React, { memo, useEffect, useRef } from 'react'; import React, { memo, useEffect } from 'react';
import { connect, useDispatch, useSelector } from 'react-redux'; import { connect, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux'; import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
import { getLogs } from 'store/actions/logs/getLogs'; import { getLogs } from 'store/actions/logs/getLogs';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { PUSH_LIVE_TAIL_EVENT } from 'types/actions/logs';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import { Container, Heading } from './styles'; import { Container, Heading } from './styles';
function LogsTable({ getLogs }) { interface LogsTableProps {
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
}
function LogsTable({ getLogs }: LogsTableProps): JSX.Element {
const { const {
searchFilter: { queryString }, searchFilter: { queryString },
logs, logs,
@ -51,7 +52,7 @@ function LogsTable({ getLogs }) {
return <Spinner height={20} tip="Getting Logs" />; return <Spinner height={20} tip="Getting Logs" />;
} }
return ( return (
<Container> <Container flex="auto">
<Heading> <Heading>
<Typography.Text <Typography.Text
style={{ style={{
@ -74,7 +75,9 @@ function LogsTable({ getLogs }) {
} }
interface DispatchProps { interface DispatchProps {
getLogs: () => (dispatch: Dispatch<AppActions>) => void; getLogs: (
props: Parameters<typeof getLogs>[0],
) => (dispatch: Dispatch<AppActions>) => void;
} }
const mapDispatchToProps = ( const mapDispatchToProps = (

View File

@ -1,4 +1,3 @@
import { grey } from '@ant-design/colors';
import { Card, Col } from 'antd'; import { Card, Col } from 'antd';
import styled from 'styled-components'; import styled from 'styled-components';
@ -9,7 +8,6 @@ export const Container = styled(Col)`
`; `;
export const Heading = styled(Card)` export const Heading = styled(Card)`
margin-bottom: 0.1rem; margin-bottom: 0.1rem;
.ant-card-body { .ant-card-body {
padding: 0.3rem 0.5rem; padding: 0.3rem 0.5rem;

View File

@ -49,7 +49,8 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString()); urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
history.replace( history.replace(
`${ROUTES.TRACE `${
ROUTES.TRACE
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`, }?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`,
); );
}; };
@ -100,17 +101,12 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString()); urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
history.replace( history.replace(
`${ROUTES.TRACE `${
ROUTES.TRACE
}?${urlParams.toString()}?selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&isFilterExclude={"serviceName":false,"status":false}&userSelectedFilter={"serviceName":["${servicename}"],"status":["error"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`, }?${urlParams.toString()}?selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&isFilterExclude={"serviceName":false,"status":false}&userSelectedFilter={"serviceName":["${servicename}"],"status":["error"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`,
); );
}; };
console.log(getWidget([
{
query: `max(sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", status_code="STATUS_CODE_ERROR"${resourceAttributePromQLQuery}}[5m]) OR rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", http_status_code=~"5.."${resourceAttributePromQLQuery}}[5m]))*100/sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER"${resourceAttributePromQLQuery}}[5m]))) < 1000 OR vector(0)`,
legend: 'Error Percentage',
},
]))
debugger;
return ( return (
<> <>
<Row gutter={24}> <Row gutter={24}>

View File

@ -18,7 +18,7 @@ const breadcrumbNameMap = {
[ROUTES.ERROR_DETAIL]: 'Errors', [ROUTES.ERROR_DETAIL]: 'Errors',
[ROUTES.LIST_ALL_ALERT]: 'Alerts', [ROUTES.LIST_ALL_ALERT]: 'Alerts',
[ROUTES.ALL_DASHBOARD]: 'Dashboard', [ROUTES.ALL_DASHBOARD]: 'Dashboard',
[ROUTES.LOGS]: 'Logs' [ROUTES.LOGS]: 'Logs',
}; };
function ShowBreadcrumbs(props: RouteComponentProps): JSX.Element { function ShowBreadcrumbs(props: RouteComponentProps): JSX.Element {

View File

@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint no-useless-escape: 0 */ /* eslint no-useless-escape: 0 */
const logqlQueries = [ const logqlQueries = [

View File

@ -1,5 +1,5 @@
import { logqlQueries } from 'lib/__fixtures__/logql'; import { logqlQueries } from 'lib/__fixtures__/logql';
import reverseParser from 'lib/logql/reverseParser'; import { reverseParser } from 'lib/logql/reverseParser';
describe('lib/logql/reverseParser', () => { describe('lib/logql/reverseParser', () => {
test('reverse parse valid queries', () => { test('reverse parse valid queries', () => {

View File

@ -1,5 +1,6 @@
import splitter from 'lib/logql/splitter';
import { logqlQueries } from 'lib/__fixtures__/logql'; import { logqlQueries } from 'lib/__fixtures__/logql';
import { splitter } from 'lib/logql/splitter';
describe('lib/logql/splitter', () => { describe('lib/logql/splitter', () => {
test('splitter valid quereies', () => { test('splitter valid quereies', () => {
logqlQueries.forEach((queryObject) => { logqlQueries.forEach((queryObject) => {

View File

@ -1,4 +1,11 @@
/* eslint-disable */
// @ts-ignore
// @ts-nocheck // @ts-nocheck
import {
ErrorConvertToFullText,
ErrorInvalidQueryPair,
} from 'lib/logql/errors';
import splitter from 'lib/logql/splitter'; import splitter from 'lib/logql/splitter';
import { import {
ConditionalOperators, ConditionalOperators,
@ -6,10 +13,6 @@ import {
QueryOperatorsSingleVal, QueryOperatorsSingleVal,
QueryTypes, QueryTypes,
} from 'lib/logql/tokens'; } from 'lib/logql/tokens';
import {
ErrorConvertToFullText,
ErrorInvalidQueryPair,
} from 'lib/logql/errors';
const validateMultiValue = (queryToken: string): boolean => { const validateMultiValue = (queryToken: string): boolean => {
const queryValues = []; const queryValues = [];

View File

@ -1,3 +1,5 @@
/* eslint-disable */
// @ts-ignore
// @ts-nocheck // @ts-nocheck
export const reverseParser = ( export const reverseParser = (

View File

@ -1,7 +1,9 @@
/* eslint-disable */
// @ts-ignore
// @ts-nocheck // @ts-nocheck
export const splitter = (queryString) => { export const splitter = (queryString: string): string[] => {
const splittedParts = []; const splittedParts: string[] = [];
let start = 0; let start = 0;
let isBracketStart = false; let isBracketStart = false;
let isQuoteStart = false; let isQuoteStart = false;
@ -13,10 +15,6 @@ export const splitter = (queryString) => {
for (let idx = 0; idx < queryString.length; idx += 1) { for (let idx = 0; idx < queryString.length; idx += 1) {
const currentChar = queryString[idx]; const currentChar = queryString[idx];
// if (start === null) {
// start = idx;
// }
if (currentChar === ' ') { if (currentChar === ' ') {
if (!isBracketStart && !isQuoteStart) { if (!isBracketStart && !isQuoteStart) {
pushPart(idx); pushPart(idx);
@ -48,7 +46,6 @@ export const splitter = (queryString) => {
pushPart(queryString.length); pushPart(queryString.length);
} }
// console.log(splittedParts.filter(Boolean));
return splittedParts.map((s) => String.raw`${s}`).filter(Boolean); return splittedParts.map((s) => String.raw`${s}`).filter(Boolean);
}; };

View File

@ -17,7 +17,6 @@ export const ConditionalOperators = {
OR: 'OR', OR: 'OR',
}; };
export const QueryTypes = { export const QueryTypes = {
QUERY_KEY: 'QUERY_KEY', QUERY_KEY: 'QUERY_KEY',
QUERY_OPERATOR: 'QUERY_OPERATOR', QUERY_OPERATOR: 'QUERY_OPERATOR',

View File

@ -1,4 +1,7 @@
export const fieldSearchFilter = (searchSpace = '', currentValue = '') => { export const fieldSearchFilter = (
searchSpace = '',
currentValue = '',
): boolean => {
if (!currentValue || !searchSpace) { if (!currentValue || !searchSpace) {
return true; return true;
} }

View File

@ -1,14 +1,14 @@
import { ILog } from 'types/api/logs/log'; import { ILog } from 'types/api/logs/log';
export function FlatLogData(log: ILog): Record<string, never> { export function FlatLogData(log: ILog): Record<string, unknown> {
let flattenLogObject: Record<string, never> = {}; const flattenLogObject: Record<string, unknown> = {};
Object.keys(log).forEach((key: string) => { Object.keys(log).forEach((key: string): void => {
if (typeof log[key] !== 'object') { if (typeof log[key as never] !== 'object') {
flattenLogObject[key] = log[key]; flattenLogObject[key] = log[key as never];
} else { } else {
Object.keys(log[key]).forEach((childKey) => { Object.keys(log[key as never]).forEach((childKey) => {
flattenLogObject[childKey] = log[key][childKey] flattenLogObject[childKey] = log[key as never][childKey];
}); });
} }
}); });

View File

@ -1,4 +1,18 @@
export const generateFilterQuery = ({ fieldKey, fieldValue, type }) => { import { QueryOperatorsMultiVal } from 'lib/logql/tokens';
type Keys = keyof typeof QueryOperatorsMultiVal;
type Values = typeof QueryOperatorsMultiVal[Keys];
interface GenerateFilterQueryParams {
fieldKey: string;
fieldValue: string;
type: Values;
}
export const generateFilterQuery = ({
fieldKey,
fieldValue,
type,
}: GenerateFilterQueryParams): string => {
let generatedQueryString = `${fieldKey} ${type} `; let generatedQueryString = `${fieldKey} ${type} `;
if (typeof fieldValue === 'number') { if (typeof fieldValue === 'number') {
generatedQueryString += `(${fieldValue})`; generatedQueryString += `(${fieldValue})`;

View File

@ -1,7 +1,7 @@
import Logs from 'container/Logs'; import Logs from 'container/Logs';
import React from 'react'; import React from 'react';
function LogsHome() { function LogsHome(): JSX.Element {
return <Logs />; return <Logs />;
} }

View File

@ -1,16 +1,17 @@
import GetSearchFields from 'api/logs/GetSearchFields'; import GetSearchFields from 'api/logs/GetSearchFields';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { GET_FIELDS, SET_FIELDS } from 'types/actions/logs'; import { SET_FIELDS } from 'types/actions/logs';
import { TraceReducer } from 'types/reducer/trace';
export const AddToSelectedField = (): ((dispatch: Dispatch<AppActions>) => void) => { export const AddToSelectedField = (): ((
return async (dispatch): void => { dispatch: Dispatch<AppActions>,
) => void) => {
return async (dispatch): Promise<void> => {
const response = await GetSearchFields(); const response = await GetSearchFields();
if (response.payload)
dispatch({ dispatch({
type: SET_FIELDS, type: SET_FIELDS,
payload: response.payload, payload: response.payload,
}); });
}; };
}; };

View File

@ -1,13 +1,12 @@
import GetSearchFields from 'api/logs/GetSearchFields'; import GetSearchFields from 'api/logs/GetSearchFields';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { GET_FIELDS, SET_FIELDS } from 'types/actions/logs'; import { SET_FIELDS } from 'types/actions/logs';
import { TraceReducer } from 'types/reducer/trace';
const IGNORED_SELECTED_FIELDS = ['timestamp']; const IGNORED_SELECTED_FIELDS = ['timestamp'];
export const GetLogsFields = (): ((dispatch: Dispatch<AppActions>) => void) => { export const GetLogsFields = (): ((dispatch: Dispatch<AppActions>) => void) => {
return async (dispatch): void => { return async (dispatch): Promise<void> => {
const response = await GetSearchFields(); const response = await GetSearchFields();
if (response.payload) { if (response.payload) {
dispatch({ dispatch({

View File

@ -2,9 +2,12 @@ import GetLogs from 'api/logs/GetLogs';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { SET_LOADING, SET_LOGS } from 'types/actions/logs'; import { SET_LOADING, SET_LOGS } from 'types/actions/logs';
import { Props } from 'types/api/logs/getLogs';
export const getLogs = (props): ((dispatch: Dispatch<AppActions>) => void) => { export const getLogs = (
return async (dispatch): void => { props: Props,
): ((dispatch: Dispatch<AppActions>) => void) => {
return async (dispatch): Promise<void> => {
dispatch({ dispatch({
type: SET_LOADING, type: SET_LOADING,
payload: true, payload: true,

View File

@ -1,19 +1,17 @@
import GetLogs from 'api/logs/GetLogs';
import GetLogsAggregate from 'api/logs/GetLogsAggregate'; import GetLogsAggregate from 'api/logs/GetLogsAggregate';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { import {
SET_LOADING,
SET_LOADING_AGGREGATE, SET_LOADING_AGGREGATE,
SET_LOGS,
SET_LOGS_AGGREGATE_SERIES, SET_LOGS_AGGREGATE_SERIES,
} from 'types/actions/logs'; } from 'types/actions/logs';
import { Props } from 'types/api/logs/getLogsAggregate';
import { ILogsAggregate } from 'types/api/logs/logAggregate'; import { ILogsAggregate } from 'types/api/logs/logAggregate';
export const getLogsAggregate = ( export const getLogsAggregate = (
props, props: Props,
): ((dispatch: Dispatch<AppActions>) => void) => { ): ((dispatch: Dispatch<AppActions>) => void) => {
return async (dispatch): void => { return async (dispatch): Promise<void> => {
dispatch({ dispatch({
type: SET_LOADING_AGGREGATE, type: SET_LOADING_AGGREGATE,
payload: true, payload: true,

View File

@ -3,7 +3,7 @@ import { combineReducers } from 'redux';
import appReducer from './app'; import appReducer from './app';
import dashboardReducer from './dashboard'; import dashboardReducer from './dashboard';
import globalTimeReducer from './global'; import globalTimeReducer from './global';
import LogsReducer from './logs'; import { LogsReducer } from './logs';
import metricsReducers from './metric'; import metricsReducers from './metric';
import { ServiceMapReducer } from './serviceMap'; import { ServiceMapReducer } from './serviceMap';
import traceReducer from './trace'; import traceReducer from './trace';

View File

@ -21,7 +21,7 @@ import {
STOP_LIVE_TAIL, STOP_LIVE_TAIL,
TOGGLE_LIVE_TAIL, TOGGLE_LIVE_TAIL,
} from 'types/actions/logs'; } from 'types/actions/logs';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
const initialState: ILogsReducer = { const initialState: ILogsReducer = {
fields: { fields: {
@ -39,33 +39,10 @@ const initialState: ILogsReducer = {
isLoading: false, isLoading: false,
isLoadingAggregate: false, isLoadingAggregate: false,
logsAggregate: [], logsAggregate: [],
detailedLog: null,
liveTail: 'STOPPED', liveTail: 'STOPPED',
liveTailStartRange: 15, liveTailStartRange: 15,
// detailedLog: { selectedLogId: null,
// timestamp: 1659360016955270100, detailedLog: null,
// id: '2CkBCauK8m3nkyKR19YhCw6WfvD',
// traceId: '',
// spanId: '',
// traceFlags: 0,
// severityText: '',
// severityNumber: 0,
// body:
// '49.207.215.17 - - [01/Aug/2022:13:20:16 +0000] "OPTIONS /api/v1/logs/fields HTTP/1.1" 200 23 "http://localhost:3301/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36" "-"\n',
// resourcesString: {
// source: 'docker',
// },
// attributesString: {
// container_id:
// 'b3c6808322609f7671c18a09515d9c84909c873471b560da65c1afe0dfd933ea',
// log_file_path:
// '/var/lib/docker/containers/b3c6808322609f7671c18a09515d9c84909c873471b560da65c1afe0dfd933ea/b3c6808322609f7671c18a09515d9c84909c873471b560da65c1afe0dfd933ea-json.log',
// stream: 'stdout',
// time: '2022-08-01T13:20:16.955270245Z',
// },
// attributesInt: {},
// attributesFloat: {},
// },
}; };
export const LogsReducer = ( export const LogsReducer = (
@ -123,9 +100,12 @@ export const LogsReducer = (
case ADD_SEARCH_FIELD_QUERY_STRING: { case ADD_SEARCH_FIELD_QUERY_STRING: {
const updatedQueryString = const updatedQueryString =
state.searchFilter.queryString + state.searchFilter.queryString ||
(state.searchFilter.queryString.length > 0 ? ' and ' : '') + `${
action.payload; state.searchFilter.queryString && state.searchFilter.queryString.length > 0
? ' and '
: ''
}${action.payload}`;
const updatedParsedQuery = parseQuery(updatedQueryString); const updatedParsedQuery = parseQuery(updatedQueryString);
return { return {

View File

@ -123,42 +123,6 @@ export interface SetLiveTailStartTime {
type: typeof SET_LIVE_TAIL_START_TIME; type: typeof SET_LIVE_TAIL_START_TIME;
payload: number; payload: number;
} }
// export interface GetServiceListLoading {
// type:
// | typeof GET_SERVICE_LIST_LOADING_START
// | typeof GET_INITIAL_APPLICATION_LOADING;
// }
// export interface GetServiceListError {
// type: typeof GET_SERVICE_LIST_ERROR | typeof GET_INITIAL_APPLICATION_ERROR;
// payload: {
// errorMessage: string;
// };
// }
// export interface GetInitialApplicationData {
// type: typeof GET_INTIAL_APPLICATION_DATA;
// payload: {
// topEndPoints: TopEndPoints[];
// // dbOverView: DBOverView[];
// // externalService: ExternalService[];
// // externalAverageDuration: ExternalAverageDuration[];
// // externalError: ExternalError[];
// serviceOverview: ServiceOverview[];
// };
// }
// export interface ResetInitialApplicationData {
// type: typeof RESET_INITIAL_APPLICATION_DATA;
// }
// export interface SetResourceAttributeQueries {
// type: typeof SET_RESOURCE_ATTRIBUTE_QUERIES;
// payload: {
// queries: IResourceAttributeQuery[];
// promQLQuery: string;
// };
// }
export type LogsActions = export type LogsActions =
| GetFields | GetFields

View File

@ -6,4 +6,8 @@ export type Props = {
limit: number; limit: number;
orderBy: string; orderBy: string;
order: string; order: string;
idGt?: string;
idLt?: string;
timestampStart?: number;
timestampEnd?: number;
}; };

View File

@ -9,4 +9,7 @@ export type Props = {
timestampStart: number; timestampStart: number;
timestampEnd: number; timestampEnd: number;
step: number; step: number;
q?: string;
idGt?: string;
idLt?: string;
}; };

View File

@ -17,7 +17,7 @@ export interface ILogsReducer {
isLoading: boolean; isLoading: boolean;
isLoadingAggregate: boolean; isLoadingAggregate: boolean;
logsAggregate: ILogsAggregate[]; logsAggregate: ILogsAggregate[];
selectedLogId: string; selectedLogId: string | null;
detailedLog: null | ILog; detailedLog: null | ILog;
liveTail: TLogsLiveTailState; liveTail: TLogsLiveTailState;
liveTailStartRange: number; // time in minutes liveTailStartRange: number; // time in minutes

File diff suppressed because it is too large Load Diff