mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-07-22 12:14:25 +08:00
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:
parent
32fba00aa8
commit
f48a884f90
@ -126,6 +126,8 @@
|
||||
"@types/copy-webpack-plugin": "^8.0.1",
|
||||
"@types/d3": "^6.2.0",
|
||||
"@types/d3-tip": "^3.5.5",
|
||||
"@types/event-source-polyfill": "^1.0.0",
|
||||
"@types/flat": "^5.0.2",
|
||||
"@types/jest": "^27.5.1",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"@types/mini-css-extract-plugin": "^2.5.1",
|
||||
|
@ -4,7 +4,7 @@ import { ENVIRONMENT } from 'constants/env';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
|
||||
export const LiveTail = (queryParams) => {
|
||||
export const LiveTail = (queryParams: string): EventSourcePolyfill => {
|
||||
const dict = {
|
||||
headers: {
|
||||
Authorization: `Bearer ${getLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN)}`,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Button, Popover, Tag, Tooltip } from 'antd';
|
||||
import { Button, Popover } from 'antd';
|
||||
import getStep from 'lib/getStep';
|
||||
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
|
||||
import React, { memo, useCallback, useMemo } from 'react';
|
||||
@ -11,15 +11,24 @@ import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { SET_SEARCH_QUERY_STRING, TOGGLE_LIVE_TAIL } from 'types/actions/logs';
|
||||
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({
|
||||
fieldKey,
|
||||
fieldValue,
|
||||
children,
|
||||
getLogs,
|
||||
getLogsAggregate,
|
||||
}) {
|
||||
}: AddToQueryHOCProps): JSX.Element {
|
||||
const {
|
||||
searchFilter: { queryString },
|
||||
logLinesPerPage,
|
||||
@ -72,9 +81,7 @@ function AddToQueryHOC({
|
||||
...(idStart ? { idGt: idStart } : {}),
|
||||
...(idEnd ? { idLt: idEnd } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
else if (liveTail === 'PLAYING') {
|
||||
} else if (liveTail === 'PLAYING') {
|
||||
dispatch({
|
||||
type: TOGGLE_LIVE_TAIL,
|
||||
payload: 'PAUSED',
|
||||
@ -88,6 +95,7 @@ function AddToQueryHOC({
|
||||
0,
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
dispatch,
|
||||
generatedQuery,
|
||||
@ -121,8 +129,12 @@ function AddToQueryHOC({
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getLogs: () => (dispatch: Dispatch<AppActions>) => void;
|
||||
getLogsAggregate: () => (dispatch: Dispatch<AppActions>) => void;
|
||||
getLogs: (
|
||||
props: Parameters<typeof getLogs>[0],
|
||||
) => (dispatch: Dispatch<AppActions>) => void;
|
||||
getLogsAggregate: (
|
||||
props: Parameters<typeof getLogsAggregate>[0],
|
||||
) => (dispatch: Dispatch<AppActions>) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
|
@ -1,19 +1,28 @@
|
||||
import { Button, Popover, Tooltip } from 'antd';
|
||||
import { Popover } from 'antd';
|
||||
import React from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
|
||||
function CopyClipboardHOC({ textToCopy, children }) {
|
||||
const [_state, setCopy] = useCopyToClipboard();
|
||||
interface CopyClipboardHOCProps {
|
||||
textToCopy: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
function CopyClipboardHOC({
|
||||
textToCopy,
|
||||
children,
|
||||
}: CopyClipboardHOCProps): JSX.Element {
|
||||
const [, setCopy] = useCopyToClipboard();
|
||||
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
cursor: 'pointer'
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => setCopy(textToCopy)}
|
||||
onClick={(): void => setCopy(textToCopy)}
|
||||
onKeyDown={(): void => setCopy(textToCopy)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<Popover
|
||||
placement="top"
|
||||
|
@ -1,22 +1,26 @@
|
||||
import { blue, grey, orange } from '@ant-design/colors';
|
||||
import { CopyFilled, CopyrightCircleFilled, ExpandAltOutlined } from '@ant-design/icons';
|
||||
import { Button, Card, Divider, Row, Typography } from 'antd';
|
||||
import { CopyFilled, ExpandAltOutlined } from '@ant-design/icons';
|
||||
import { Button, Divider, Row, Typography } from 'antd';
|
||||
import { map } from 'd3';
|
||||
import dayjs from 'dayjs';
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { flatMap, flatMapDeep } from 'lodash-es';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { AppState } from 'store/reducers';
|
||||
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 CopyClipboardHOC from '../CopyClipboardHOC';
|
||||
import { Container } from './styles';
|
||||
|
||||
function LogGeneralField({ fieldKey, fieldValue }) {
|
||||
interface LogFieldProps {
|
||||
fieldKey: string;
|
||||
fieldValue: string;
|
||||
}
|
||||
function LogGeneralField({ fieldKey, fieldValue }: LogFieldProps): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@ -35,8 +39,10 @@ function LogGeneralField({ fieldKey, fieldValue }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LogSelectedField({ fieldKey = '', fieldValue = '' }) {
|
||||
function LogSelectedField({
|
||||
fieldKey = '',
|
||||
fieldValue = '',
|
||||
}: LogFieldProps): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@ -66,13 +72,16 @@ function LogSelectedField({ fieldKey = '', fieldValue = '' }) {
|
||||
);
|
||||
}
|
||||
|
||||
function LogItem({ logData }) {
|
||||
interface LogItemProps {
|
||||
logData: ILog;
|
||||
}
|
||||
function LogItem({ logData }: LogItemProps): JSX.Element {
|
||||
const {
|
||||
fields: { selected },
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
const dispatch = useDispatch();
|
||||
const flattenLogData = useMemo(() => FlatLogData(logData), [logData]);
|
||||
const [_state, setCopy] = useCopyToClipboard();
|
||||
const [, setCopy] = useCopyToClipboard();
|
||||
|
||||
const handleDetailedView = useCallback(() => {
|
||||
dispatch({
|
||||
@ -81,22 +90,28 @@ function LogItem({ logData }) {
|
||||
});
|
||||
}, [dispatch, logData]);
|
||||
|
||||
const handleCopyJSON = () => {
|
||||
setCopy(JSON.stringify(logData, null, 2))
|
||||
}
|
||||
const handleCopyJSON = (): void => {
|
||||
setCopy(JSON.stringify(logData, null, 2));
|
||||
};
|
||||
return (
|
||||
<Container>
|
||||
<div style={{ maxWidth: '100%' }}>
|
||||
<div>
|
||||
{'{'}
|
||||
<div style={{ marginLeft: '0.5rem' }}>
|
||||
<LogGeneralField fieldKey="log" fieldValue={flattenLogData.body} />
|
||||
<LogGeneralField
|
||||
fieldKey="log"
|
||||
fieldValue={flattenLogData.body as never}
|
||||
/>
|
||||
{flattenLogData.stream && (
|
||||
<LogGeneralField fieldKey="stream" fieldValue={flattenLogData.stream} />
|
||||
<LogGeneralField
|
||||
fieldKey="stream"
|
||||
fieldValue={flattenLogData.stream as never}
|
||||
/>
|
||||
)}
|
||||
<LogGeneralField
|
||||
fieldKey="timestamp"
|
||||
fieldValue={dayjs(flattenLogData.timestamp / 1e6).format()}
|
||||
fieldValue={dayjs((flattenLogData.timestamp as never) / 1e6).format()}
|
||||
/>
|
||||
</div>
|
||||
{'}'}
|
||||
@ -107,7 +122,7 @@ function LogItem({ logData }) {
|
||||
<LogSelectedField
|
||||
key={field.name}
|
||||
fieldKey={field.name}
|
||||
fieldValue={flattenLogData[field.name]}
|
||||
fieldValue={flattenLogData[field.name] as never}
|
||||
/>
|
||||
) : null;
|
||||
})}
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
|
@ -87,7 +87,7 @@ function Retention({
|
||||
<Col span={12} style={{ display: 'flex' }}>
|
||||
<RetentionFieldLabel>{text}</RetentionFieldLabel>
|
||||
</Col>
|
||||
<Row span={12} justify="end">
|
||||
<Row justify="end">
|
||||
<RetentionFieldInputContainer>
|
||||
<Input
|
||||
value={selectedValue && selectedValue >= 0 ? selectedValue : ''}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {
|
||||
ArrowLeftOutlined,
|
||||
FastBackwardOutlined,
|
||||
LeftOutlined,
|
||||
RightOutlined,
|
||||
@ -19,7 +18,7 @@ import {
|
||||
SET_LOG_LINES_PER_PAGE,
|
||||
} from 'types/actions/logs';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import ILogsReducer from 'types/reducer/logs';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import { Container } from './styles';
|
||||
|
||||
@ -27,7 +26,10 @@ const { Option } = Select;
|
||||
|
||||
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>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
@ -40,14 +42,14 @@ function LogControls({ getLogs }) {
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleLogLinesPerPageChange = (e: number) => {
|
||||
const handleLogLinesPerPageChange = (e: number): void => {
|
||||
dispatch({
|
||||
type: SET_LOG_LINES_PER_PAGE,
|
||||
payload: e,
|
||||
});
|
||||
};
|
||||
|
||||
const handleGoToLatest = () => {
|
||||
const handleGoToLatest = (): void => {
|
||||
dispatch({
|
||||
type: RESET_ID_START_AND_END,
|
||||
});
|
||||
@ -65,12 +67,12 @@ function LogControls({ getLogs }) {
|
||||
});
|
||||
};
|
||||
|
||||
const handleNavigatePrevious = () => {
|
||||
const handleNavigatePrevious = (): void => {
|
||||
dispatch({
|
||||
type: GET_PREVIOUS_LOG_LINES,
|
||||
});
|
||||
};
|
||||
const handleNavigateNext = () => {
|
||||
const handleNavigateNext = (): void => {
|
||||
dispatch({
|
||||
type: GET_NEXT_LOG_LINES,
|
||||
});
|
||||
@ -105,7 +107,9 @@ function LogControls({ getLogs }) {
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getLogs: () => (dispatch: Dispatch<AppActions>) => void;
|
||||
getLogs: (
|
||||
props: Parameters<typeof getLogs>[0],
|
||||
) => (dispatch: Dispatch<AppActions>) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
|
@ -2,7 +2,7 @@ import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons';
|
||||
import { Button, Col, Popover } from 'antd';
|
||||
import getStep from 'lib/getStep';
|
||||
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 { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
@ -12,9 +12,9 @@ import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { SET_SEARCH_QUERY_STRING, TOGGLE_LIVE_TAIL } from 'types/actions/logs';
|
||||
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) {
|
||||
return s;
|
||||
}
|
||||
@ -24,7 +24,21 @@ const removeJSONStringifyQuotes = (s: string) => {
|
||||
}
|
||||
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 {
|
||||
searchFilter: { queryString },
|
||||
logLinesPerPage,
|
||||
@ -38,7 +52,7 @@ function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const handleQueryAdd = (newQueryString) => {
|
||||
const handleQueryAdd = (newQueryString: string): void => {
|
||||
let updatedQueryString = queryString || '';
|
||||
|
||||
if (updatedQueryString.length === 0) {
|
||||
@ -94,7 +108,7 @@ function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={() =>
|
||||
onClick={(): void =>
|
||||
handleQueryAdd(
|
||||
generateFilterQuery({
|
||||
fieldKey,
|
||||
@ -110,7 +124,7 @@ function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={() =>
|
||||
onClick={(): void =>
|
||||
handleQueryAdd(
|
||||
generateFilterQuery({
|
||||
fieldKey,
|
||||
@ -124,7 +138,8 @@ function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
|
||||
</Button>
|
||||
</Col>
|
||||
),
|
||||
[],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[fieldKey, validatedFieldValue],
|
||||
);
|
||||
return (
|
||||
<Popover placement="bottomLeft" content={PopOverMenuContent} trigger="click">
|
||||
@ -134,10 +149,11 @@ function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getLogs: () => (dispatch: Dispatch<AppActions>) => void;
|
||||
getLogsAggregate: () => (dispatch: Dispatch<AppActions>) => void;
|
||||
getLogs: (props: Parameters<typeof getLogs>[0]) => (dispatch: never) => void;
|
||||
getLogsAggregate: (
|
||||
props: Parameters<typeof getLogsAggregate>[0],
|
||||
) => (dispatch: never) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
@ -147,4 +163,5 @@ const mapDispatchToProps = (
|
||||
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));
|
||||
|
@ -4,31 +4,41 @@ import { Button, Row } from 'antd';
|
||||
import Editor from 'components/Editor';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
function JSONView({ logData }) {
|
||||
const [_state, copyToClipboard] = useCopyToClipboard();
|
||||
const LogJsonData = useMemo(() => JSON.stringify(logData, null, 2), [logData]);
|
||||
return (
|
||||
<div>
|
||||
<Row
|
||||
style={{
|
||||
justifyContent: 'flex-end',
|
||||
margin: '0.5rem 0',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={(): void => copyToClipboard(LogJsonData)}
|
||||
>
|
||||
<CopyFilled /> <span style={{ color: blue[5] }}>Copy to Clipboard</span>
|
||||
</Button>
|
||||
</Row>
|
||||
<div style={{ marginTop: '0.5rem' }}>
|
||||
<Editor value={LogJsonData} language="json" height="70vh" readOnly />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
interface JSONViewProps {
|
||||
logData: ILog;
|
||||
}
|
||||
function JSONView({ logData }: JSONViewProps): JSX.Element {
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
const LogJsonData = useMemo(() => JSON.stringify(logData, null, 2), [logData]);
|
||||
return (
|
||||
<div>
|
||||
<Row
|
||||
style={{
|
||||
justifyContent: 'flex-end',
|
||||
margin: '0.5rem 0',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={(): void => copyToClipboard(LogJsonData)}
|
||||
>
|
||||
<CopyFilled /> <span style={{ color: blue[5] }}>Copy to Clipboard</span>
|
||||
</Button>
|
||||
</Row>
|
||||
<div style={{ marginTop: '0.5rem' }}>
|
||||
<Editor
|
||||
value={LogJsonData}
|
||||
language="json"
|
||||
height="70vh"
|
||||
readOnly
|
||||
onChange={(): void => {}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default JSONView;
|
||||
|
@ -1,47 +1,52 @@
|
||||
import { blue, orange } from '@ant-design/colors';
|
||||
import {
|
||||
MenuFoldOutlined,
|
||||
MinusCircleOutlined,
|
||||
PlusCircleFilled,
|
||||
PlusCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Button, Col, Input, Popover, Table, Typography } from 'antd';
|
||||
import { Input, Table } from 'antd';
|
||||
import AddToQueryHOC from 'components/Logs/AddToQueryHOC';
|
||||
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
|
||||
import flatten from 'flat';
|
||||
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
import ActionItem from './ActionItem';
|
||||
|
||||
// Fields which should be restricted from adding it to query
|
||||
const RESTRICTED_FIELDS = ['timestamp'];
|
||||
|
||||
function TableView({ logData }) {
|
||||
interface TableViewProps {
|
||||
logData: ILog;
|
||||
}
|
||||
function TableView({ logData }: TableViewProps): JSX.Element | null {
|
||||
const [fieldSearchInput, setFieldSearchInput] = useState<string>('');
|
||||
|
||||
const flattenLogData = useMemo(() => (logData ? flatten(logData) : null), [
|
||||
logData,
|
||||
]);
|
||||
const flattenLogData: Record<string, never> | null = useMemo(
|
||||
() => (logData ? flatten(logData) : null),
|
||||
[logData],
|
||||
);
|
||||
if (logData === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dataSource = Object.keys(flattenLogData)
|
||||
.filter((field) => fieldSearchFilter(field, fieldSearchInput))
|
||||
.map((key) => {
|
||||
return {
|
||||
key,
|
||||
field: key,
|
||||
value: JSON.stringify(flattenLogData[key]),
|
||||
};
|
||||
});
|
||||
const dataSource =
|
||||
flattenLogData !== null &&
|
||||
Object.keys(flattenLogData)
|
||||
.filter((field) => fieldSearchFilter(field, fieldSearchInput))
|
||||
.map((key) => {
|
||||
return {
|
||||
key,
|
||||
field: key,
|
||||
value: JSON.stringify(flattenLogData[key]),
|
||||
};
|
||||
});
|
||||
|
||||
if (!dataSource) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Action',
|
||||
width: 75,
|
||||
render: (fieldData) => {
|
||||
render: (fieldData: Record<string, string>): JSX.Element | null => {
|
||||
const fieldKey = fieldData.field.split('.').slice(-1);
|
||||
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
|
||||
return <ActionItem fieldKey={fieldKey} fieldValue={fieldData.value} />;
|
||||
@ -54,13 +59,13 @@ function TableView({ logData }) {
|
||||
dataIndex: 'field',
|
||||
key: 'field',
|
||||
width: '35%',
|
||||
render: (field: string) => {
|
||||
render: (field: string): JSX.Element => {
|
||||
const fieldKey = field.split('.').slice(-1);
|
||||
const renderedField = <span style={{ color: blue[4] }}>{field}</span>;
|
||||
|
||||
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
|
||||
return (
|
||||
<AddToQueryHOC fieldKey={fieldKey} fieldValue={flattenLogData[field]}>
|
||||
<AddToQueryHOC fieldKey={fieldKey[0]} fieldValue={flattenLogData[field]}>
|
||||
{' '}
|
||||
{renderedField}
|
||||
</AddToQueryHOC>
|
||||
@ -74,7 +79,7 @@ function TableView({ logData }) {
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
ellipsis: false,
|
||||
render: (field) => (
|
||||
render: (field: never): JSX.Element => (
|
||||
<CopyClipboardHOC textToCopy={field}>
|
||||
<span style={{ color: orange[6] }}>{field}</span>
|
||||
</CopyClipboardHOC>
|
||||
@ -88,13 +93,13 @@ function TableView({ logData }) {
|
||||
placeholder="Search field names"
|
||||
size="large"
|
||||
value={fieldSearchInput}
|
||||
onChange={(e) => setFieldSearchInput(e.target.value)}
|
||||
onChange={(e): void => setFieldSearchInput(e.target.value)}
|
||||
/>
|
||||
<Table
|
||||
// scroll={{ x: true }}
|
||||
tableLayout="fixed"
|
||||
dataSource={dataSource}
|
||||
columns={columns}
|
||||
columns={columns as never}
|
||||
pagination={false}
|
||||
/>
|
||||
</div>
|
||||
|
@ -3,49 +3,51 @@ import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
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 TableView from './TableView';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
function LogDetailedView() {
|
||||
const { detailedLog } = useSelector<AppState, ILogsReducer>(
|
||||
(state) => state.logs,
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
const onDrawerClose = () => {
|
||||
dispatch({
|
||||
type: SET_DETAILED_LOG_DATA,
|
||||
payload: null,
|
||||
});
|
||||
};
|
||||
function LogDetailedView(): JSX.Element {
|
||||
const { detailedLog } = useSelector<AppState, ILogsReducer>(
|
||||
(state) => state.logs,
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
const onDrawerClose = (): void => {
|
||||
dispatch({
|
||||
type: SET_DETAILED_LOG_DATA,
|
||||
payload: null,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{}}>
|
||||
<Drawer
|
||||
width="60%"
|
||||
title="Log Details"
|
||||
placement="right"
|
||||
closable
|
||||
mask={false}
|
||||
onClose={onDrawerClose}
|
||||
visible={detailedLog !== null}
|
||||
getContainer={false}
|
||||
style={{ overscrollBehavior: 'contain' }}
|
||||
>
|
||||
<Tabs defaultActiveKey="1">
|
||||
<TabPane tab="Table" key="1">
|
||||
<TableView logData={detailedLog} />
|
||||
</TabPane>
|
||||
<TabPane tab="JSON" key="2">
|
||||
<JSONView logData={detailedLog} />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div style={{}}>
|
||||
<Drawer
|
||||
width="60%"
|
||||
title="Log Details"
|
||||
placement="right"
|
||||
closable
|
||||
mask={false}
|
||||
onClose={onDrawerClose}
|
||||
visible={detailedLog !== null}
|
||||
getContainer={false}
|
||||
style={{ overscrollBehavior: 'contain' }}
|
||||
>
|
||||
{detailedLog && (
|
||||
<Tabs defaultActiveKey="1">
|
||||
<TabPane tab="Table" key="1">
|
||||
<TableView logData={detailedLog} />
|
||||
</TabPane>
|
||||
<TabPane tab="JSON" key="2">
|
||||
<JSONView logData={detailedLog} />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
)}
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LogDetailedView;
|
||||
|
@ -1,23 +1,26 @@
|
||||
import React from 'react';
|
||||
|
||||
function OptionIcon({ isDarkMode }) {
|
||||
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
|
||||
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>
|
||||
);
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default OptionIcon;
|
||||
|
@ -1,27 +1,11 @@
|
||||
import { green, red } from '@ant-design/colors';
|
||||
import {
|
||||
ArrowRightOutlined,
|
||||
IeSquareFilled,
|
||||
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';
|
||||
/* 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 { LiveTail } from 'api/logs/livetail';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import dayjs from 'dayjs';
|
||||
import { debounce, throttle } from 'lodash-es';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { throttle } from 'lodash-es';
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import {
|
||||
@ -32,7 +16,7 @@ import {
|
||||
} from 'types/actions/logs';
|
||||
import { TLogsLiveTailState } from 'types/api/logs/liveTail';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import ILogsReducer from 'types/reducer/logs';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import OptionIcon from './OptionIcon';
|
||||
import { TimePickerCard, TimePickerSelect } from './styles';
|
||||
@ -66,7 +50,7 @@ const TIME_PICKER_OPTIONS = [
|
||||
},
|
||||
];
|
||||
|
||||
function LogLiveTail() {
|
||||
function LogLiveTail(): JSX.Element {
|
||||
const {
|
||||
liveTail,
|
||||
searchFilter: { queryString },
|
||||
@ -75,15 +59,16 @@ function LogLiveTail() {
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const dispatch = useDispatch();
|
||||
const handleLiveTail = (toggleState: TLogsLiveTailState) => {
|
||||
const handleLiveTail = (toggleState: TLogsLiveTailState): void => {
|
||||
dispatch({
|
||||
type: TOGGLE_LIVE_TAIL,
|
||||
payload: toggleState,
|
||||
});
|
||||
};
|
||||
|
||||
const batchedEventsRef = useRef([]);
|
||||
const batchedEventsRef = useRef<Record<string, unknown>[]>([]);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const pushLiveLog = useCallback(
|
||||
throttle(() => {
|
||||
dispatch({
|
||||
@ -96,46 +81,42 @@ function LogLiveTail() {
|
||||
[],
|
||||
);
|
||||
|
||||
const batchLiveLog = (e) => {
|
||||
const batchLiveLog = (e: { data: string }): void => {
|
||||
// console.log('EVENT BATCHED');
|
||||
batchedEventsRef.current.push(JSON.parse(e.data));
|
||||
batchedEventsRef.current.push(JSON.parse(e.data as string) as never);
|
||||
pushLiveLog();
|
||||
};
|
||||
|
||||
// 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(() => {
|
||||
if (liveTail === 'PLAYING') {
|
||||
// console.log('Starting Live Tail', logs.length);
|
||||
const timeStamp = dayjs().subtract(liveTailStartRange, 'minute').valueOf();
|
||||
const queryParams = new URLSearchParams({
|
||||
...(queryString ? { q: queryString } : {}),
|
||||
timestampStart: timeStamp * 1e6,
|
||||
timestampStart: (timeStamp * 1e6) as never,
|
||||
...(liveTailSourceRef.current && logs.length > 0
|
||||
? {
|
||||
idGt: logs[0].id,
|
||||
}
|
||||
idGt: logs[0].id,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
const source = LiveTail(queryParams.toString());
|
||||
liveTailSourceRef.current = source;
|
||||
source.onmessage = function (e) {
|
||||
// pushLiveLog(e)
|
||||
source.onmessage = function connectionMessage(e): void {
|
||||
batchLiveLog(e);
|
||||
};
|
||||
source.onopen = function (event) {
|
||||
// console.log('open event');
|
||||
// console.log(event);
|
||||
};
|
||||
source.onerror = function (event) {
|
||||
// console.log(event);
|
||||
// source.onopen = function connectionOpen(): void { };
|
||||
source.onerror = function connectionError(event: unknown): void {
|
||||
console.error(event);
|
||||
source.close();
|
||||
dispatch({
|
||||
type: TOGGLE_LIVE_TAIL,
|
||||
payload: false,
|
||||
});
|
||||
};
|
||||
} else if (liveTailSourceRef.current) {
|
||||
} else if (liveTailSourceRef.current && liveTailSourceRef.current.close) {
|
||||
liveTailSourceRef.current?.close();
|
||||
}
|
||||
|
||||
@ -144,7 +125,7 @@ function LogLiveTail() {
|
||||
}
|
||||
}, [liveTail]);
|
||||
|
||||
const handleLiveTailStart = () => {
|
||||
const handleLiveTailStart = (): void => {
|
||||
handleLiveTail('PLAYING');
|
||||
if (!liveTailSourceRef.current) {
|
||||
dispatch({
|
||||
@ -158,7 +139,7 @@ function LogLiveTail() {
|
||||
<TimePickerSelect
|
||||
disabled={liveTail === 'PLAYING'}
|
||||
value={liveTailStartRange}
|
||||
onChange={(value) => {
|
||||
onChange={(value): void => {
|
||||
dispatch({
|
||||
type: SET_LIVE_TAIL_START_TIME,
|
||||
payload: value,
|
||||
@ -183,7 +164,7 @@ function LogLiveTail() {
|
||||
{liveTail === 'PLAYING' ? (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => handleLiveTail('PAUSED')}
|
||||
onClick={(): void => handleLiveTail('PAUSED')}
|
||||
title="Pause live tail"
|
||||
style={{ background: green[6] }}
|
||||
>
|
||||
@ -201,7 +182,7 @@ function LogLiveTail() {
|
||||
{liveTail !== 'STOPPED' && (
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => handleLiveTail('STOPPED')}
|
||||
onClick={(): void => handleLiveTail('STOPPED')}
|
||||
title="Exit live tail"
|
||||
>
|
||||
<div
|
||||
|
@ -15,7 +15,10 @@ import { GetLogsFields } from 'store/actions/logs/getFields';
|
||||
import AppActions from 'types/actions';
|
||||
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 urlQuery = useMemo(() => {
|
||||
@ -29,7 +32,7 @@ function Logs({ getLogsFields }) {
|
||||
type: SET_SEARCH_QUERY_STRING,
|
||||
payload: urlQuery.get('q'),
|
||||
});
|
||||
}, [dispatch]);
|
||||
}, [dispatch, urlQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
getLogsFields();
|
||||
@ -39,16 +42,16 @@ function Logs({ getLogsFields }) {
|
||||
<div style={{ position: 'relative' }}>
|
||||
<Row style={{ justifyContent: 'center', alignItems: 'center' }}>
|
||||
<SearchFilter />
|
||||
<Divider type='vertical' style={{ height: '2rem' }} />
|
||||
<Divider type="vertical" style={{ height: '2rem' }} />
|
||||
<LogLiveTail />
|
||||
</Row>
|
||||
<LogsAggregate />
|
||||
<LogControls />
|
||||
<Divider style={{ margin: 0 }} />
|
||||
<Row gutter={20} style={{ flexWrap: 'nowrap' }}>
|
||||
<LogsFilters flex="450px" />
|
||||
<LogsFilters />
|
||||
<Divider type="vertical" style={{ height: '100%', margin: 0 }} />
|
||||
<LogsTable flex="auto" />
|
||||
<LogsTable />
|
||||
</Row>
|
||||
<LogDetailedView />
|
||||
</div>
|
||||
|
@ -1,42 +1,41 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { blue } from '@ant-design/colors';
|
||||
import Graph from 'components/Graph';
|
||||
import Spinner from 'components/Spinner';
|
||||
import dayjs from 'dayjs';
|
||||
import getStep from 'lib/getStep';
|
||||
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 { ThunkDispatch } from 'redux-thunk';
|
||||
import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import ILogsReducer from 'types/reducer/logs';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import { Container } from './styles';
|
||||
|
||||
function LogsAggregate({ getLogsAggregate }) {
|
||||
interface LogsAggregateProps {
|
||||
getLogsAggregate: (arg0: Parameters<typeof getLogsAggregate>[0]) => void;
|
||||
}
|
||||
function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element {
|
||||
const {
|
||||
searchFilter: { queryString },
|
||||
logs,
|
||||
logLinesPerPage,
|
||||
idEnd,
|
||||
idStart,
|
||||
isLoading,
|
||||
isLoadingAggregate,
|
||||
logsAggregate,
|
||||
liveTail,
|
||||
liveTailStartRange,
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const reFetchIntervalRef = useRef(null);
|
||||
const reFetchIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
useEffect(() => {
|
||||
// console.log('LIVE TAIL LOG AGG', liveTail)
|
||||
switch (liveTail) {
|
||||
case 'STOPPED': {
|
||||
if (reFetchIntervalRef.current) {
|
||||
@ -59,7 +58,7 @@ function LogsAggregate({ getLogsAggregate }) {
|
||||
}
|
||||
|
||||
case 'PLAYING': {
|
||||
const aggregateCall = () => {
|
||||
const aggregateCall = (): void => {
|
||||
const startTime =
|
||||
dayjs().subtract(liveTailStartRange, 'minute').valueOf() * 1e6;
|
||||
const endTime = dayjs().valueOf() * 1e6;
|
||||
@ -72,17 +71,15 @@ function LogsAggregate({ getLogsAggregate }) {
|
||||
inputFormat: 'ns',
|
||||
}),
|
||||
q: queryString,
|
||||
...(idStart ? {idGt: idStart } : {}),
|
||||
...(idStart ? { idGt: idStart } : {}),
|
||||
...(idEnd ? { idLt: idEnd } : {}),
|
||||
});
|
||||
};
|
||||
aggregateCall();
|
||||
reFetchIntervalRef.current = setInterval(aggregateCall, 60000);
|
||||
// console.log('LA Play', reFetchIntervalRef.current);
|
||||
break;
|
||||
}
|
||||
case 'PAUSED': {
|
||||
// console.log('LA Pause', reFetchIntervalRef.current);
|
||||
if (reFetchIntervalRef.current) {
|
||||
clearInterval(reFetchIntervalRef.current);
|
||||
}
|
||||
@ -98,7 +95,6 @@ function LogsAggregate({ getLogsAggregate }) {
|
||||
labels: logsAggregate.map((s) => new Date(s.timestamp / 1000000)),
|
||||
datasets: [
|
||||
{
|
||||
// label: 'Span Count',
|
||||
data: logsAggregate.map((s) => s.value),
|
||||
backgroundColor: blue[4],
|
||||
},
|
||||
@ -123,7 +119,9 @@ function LogsAggregate({ getLogsAggregate }) {
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getLogsAggregate: () => (dispatch: Dispatch<AppActions>) => void;
|
||||
getLogsAggregate: (
|
||||
props: Parameters<typeof getLogsAggregate>[0],
|
||||
) => (dispatch: Dispatch<AppActions>) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
|
@ -1,18 +1,21 @@
|
||||
import {
|
||||
CloseCircleFilled,
|
||||
CloseOutlined,
|
||||
LoadingOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { Button, Popover, Spin } from 'antd';
|
||||
import Spinner from 'components/Spinner';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useHover, useHoverDirty } from 'react-use';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
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({
|
||||
name,
|
||||
buttonIcon,
|
||||
@ -20,16 +23,16 @@ export function FieldItem({
|
||||
fieldData,
|
||||
fieldIndex,
|
||||
isLoading,
|
||||
iconHoverText
|
||||
}) {
|
||||
iconHoverText,
|
||||
}: FieldItemProps): JSX.Element {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
return (
|
||||
<Field
|
||||
onMouseEnter={() => {
|
||||
onMouseEnter={(): void => {
|
||||
setIsHovered(true);
|
||||
}}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
onMouseLeave={(): void => setIsHovered(false)}
|
||||
isDarkMode={isDarkMode}
|
||||
>
|
||||
<span>{name}</span>
|
||||
@ -43,7 +46,7 @@ export function FieldItem({
|
||||
type="text"
|
||||
size="small"
|
||||
icon={buttonIcon}
|
||||
onClick={() => buttonOnClick({ fieldData, fieldIndex })}
|
||||
onClick={(): void => buttonOnClick({ fieldData, fieldIndex })}
|
||||
style={{ color: 'inherit', padding: 0, height: '1rem', width: '1rem' }}
|
||||
/>
|
||||
</Popover>
|
||||
|
@ -1,10 +1,6 @@
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
import { red } from '@ant-design/colors';
|
||||
import {
|
||||
CloseCircleFilled,
|
||||
CloseOutlined,
|
||||
PlusCircleFilled,
|
||||
PlusCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { CloseOutlined, PlusCircleFilled } from '@ant-design/icons';
|
||||
import { Input } from 'antd';
|
||||
import AddToSelectedFields from 'api/logs/AddToSelectedField';
|
||||
import RemoveSelectedField from 'api/logs/RemoveFromSelectedField';
|
||||
@ -17,34 +13,40 @@ import { ThunkDispatch } from 'redux-thunk';
|
||||
import { GetLogsFields } from 'store/actions/logs/getFields';
|
||||
import { AppState } from 'store/reducers';
|
||||
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 {
|
||||
CategoryContainer,
|
||||
Container,
|
||||
ExtractField,
|
||||
Field,
|
||||
FieldContainer,
|
||||
} from './styles';
|
||||
import { CategoryContainer, Container, FieldContainer } from './styles';
|
||||
|
||||
const RESTRICTED_SELECTED_FIELDS = ['timestamp', 'id'];
|
||||
|
||||
function LogsFilters({ getLogsFields }) {
|
||||
interface LogsFiltersProps {
|
||||
getLogsFields: () => void;
|
||||
}
|
||||
function LogsFilters({ getLogsFields }: LogsFiltersProps): JSX.Element {
|
||||
const {
|
||||
fields: { interesting, selected },
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
|
||||
const [selectedFieldLoading, setSelectedFieldLoading] = useState([]);
|
||||
const [interestingFieldLoading, setInterestingFieldLoading] = useState([]);
|
||||
const [selectedFieldLoading, setSelectedFieldLoading] = useState<number[]>([]);
|
||||
const [interestingFieldLoading, setInterestingFieldLoading] = useState<
|
||||
number[]
|
||||
>([]);
|
||||
|
||||
const [filterValuesInput, setFilterValuesInput] = useState('');
|
||||
const handleSearch = (e) => {
|
||||
setFilterValuesInput(e.target.value);
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
setFilterValuesInput((e.target as HTMLInputElement).value);
|
||||
};
|
||||
|
||||
const handleAddInterestingToSelected = async ({ fieldData, fieldIndex }) => {
|
||||
setInterestingFieldLoading((prevState) => {
|
||||
const handleAddInterestingToSelected = async ({
|
||||
fieldData,
|
||||
fieldIndex,
|
||||
}: {
|
||||
fieldData: IInterestingFields;
|
||||
fieldIndex: number;
|
||||
}): Promise<void> => {
|
||||
setInterestingFieldLoading((prevState: number[]) => {
|
||||
prevState.push(fieldIndex);
|
||||
return [...prevState];
|
||||
});
|
||||
@ -59,7 +61,13 @@ function LogsFilters({ getLogsFields }) {
|
||||
interestingFieldLoading.filter((e) => e !== fieldIndex),
|
||||
);
|
||||
};
|
||||
const handleRemoveSelectedField = async ({ fieldData, fieldIndex }) => {
|
||||
const handleRemoveSelectedField = async ({
|
||||
fieldData,
|
||||
fieldIndex,
|
||||
}: {
|
||||
fieldData: ISelectedFields;
|
||||
fieldIndex: number;
|
||||
}): Promise<void> => {
|
||||
setSelectedFieldLoading((prevState) => {
|
||||
prevState.push(fieldIndex);
|
||||
return [...prevState];
|
||||
@ -77,7 +85,7 @@ function LogsFilters({ getLogsFields }) {
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Container>
|
||||
<Container flex="450px">
|
||||
<Input
|
||||
placeholder="Filter Values"
|
||||
onInput={handleSearch}
|
||||
@ -93,14 +101,14 @@ function LogsFilters({ getLogsFields }) {
|
||||
.filter((field) => fieldSearchFilter(field.name, filterValuesInput))
|
||||
.map((field, idx) => (
|
||||
<FieldItem
|
||||
key={field + idx}
|
||||
key={`${JSON.stringify(field)}-${idx}`}
|
||||
name={field.name}
|
||||
fieldData={field}
|
||||
fieldData={field as never}
|
||||
fieldIndex={idx}
|
||||
buttonIcon={<CloseOutlined style={{ color: red[5] }} />}
|
||||
buttonOnClick={
|
||||
!RESTRICTED_SELECTED_FIELDS.includes(field.name) &&
|
||||
handleRemoveSelectedField
|
||||
(!RESTRICTED_SELECTED_FIELDS.includes(field.name) &&
|
||||
handleRemoveSelectedField) as never
|
||||
}
|
||||
isLoading={selectedFieldLoading.includes(idx)}
|
||||
iconHoverText="Remove from Selected Fields"
|
||||
@ -115,12 +123,12 @@ function LogsFilters({ getLogsFields }) {
|
||||
.filter((field) => fieldSearchFilter(field.name, filterValuesInput))
|
||||
.map((field, idx) => (
|
||||
<FieldItem
|
||||
key={field + idx}
|
||||
key={`${JSON.stringify(field)}-${idx}`}
|
||||
name={field.name}
|
||||
fieldData={field}
|
||||
fieldData={field as never}
|
||||
fieldIndex={idx}
|
||||
buttonIcon={<PlusCircleFilled />}
|
||||
buttonOnClick={handleAddInterestingToSelected}
|
||||
buttonOnClick={handleAddInterestingToSelected as never}
|
||||
isLoading={interestingFieldLoading.includes(idx)}
|
||||
iconHoverText="Add to Selected Fields"
|
||||
/>
|
||||
|
@ -1,15 +1,20 @@
|
||||
import { Typography } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
function FieldKey({ name, type }) {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
interface FieldKeyProps {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
function FieldKey({ name, type }: FieldKeyProps): JSX.Element {
|
||||
return (
|
||||
<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;
|
||||
|
@ -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 { Button, Input, Select, Typography } from 'antd';
|
||||
import { Button, Input, Select } from 'antd';
|
||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||
import {
|
||||
ConditionalOperators,
|
||||
QueryOperatorsMultiVal,
|
||||
QueryOperatorsSingleVal,
|
||||
QueryTypes,
|
||||
} from 'lib/logql/tokens';
|
||||
import { chunk, cloneDeep, debounce, flatten } from 'lodash-es';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { flatten } from 'lodash-es';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useHoverDirty, useLocation } from 'react-use';
|
||||
import { AppState } from 'store/reducers';
|
||||
import ILogsReducer from 'types/reducer/logs';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import FieldKey from '../FieldKey';
|
||||
@ -26,12 +24,24 @@ import { QueryConditionContainer, QueryFieldContainer } from '../styles';
|
||||
import { createParsedQueryStructure } from '../utils';
|
||||
|
||||
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 {
|
||||
fields: { selected },
|
||||
} = 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) {
|
||||
if (inputKey === selectedField.name) {
|
||||
return selectedField.type;
|
||||
@ -39,11 +49,10 @@ function QueryField({ query, queryIndex, onUpdate, onDelete }) {
|
||||
}
|
||||
return '';
|
||||
};
|
||||
const fieldType = useMemo(() => getFieldType(query[0].value), [
|
||||
const fieldType = useMemo(() => getFieldType(query[0].value as string), [
|
||||
query,
|
||||
selected,
|
||||
]);
|
||||
const handleChange = (qIdx, value) => {
|
||||
const handleChange = (qIdx: number, value: string): void => {
|
||||
query[qIdx].value = value || '';
|
||||
|
||||
if (qIdx === 1) {
|
||||
@ -51,16 +60,17 @@ function QueryField({ query, queryIndex, onUpdate, onDelete }) {
|
||||
if (!Array.isArray(query[2].value)) {
|
||||
query[2].value = [];
|
||||
}
|
||||
} else if (Object.values(QueryOperatorsSingleVal).includes(value)) {
|
||||
if (Array.isArray(query[2].value)) {
|
||||
query[2].value = '';
|
||||
}
|
||||
} else if (
|
||||
Object.values(QueryOperatorsSingleVal).includes(value) &&
|
||||
Array.isArray(query[2].value)
|
||||
) {
|
||||
query[2].value = '';
|
||||
}
|
||||
}
|
||||
onUpdate(query, queryIndex);
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
const handleClear = (): void => {
|
||||
onDelete(queryIndex);
|
||||
};
|
||||
if (!Array.isArray(query)) {
|
||||
@ -72,39 +82,43 @@ function QueryField({ query, queryIndex, onUpdate, onDelete }) {
|
||||
style={{ ...(queryIndex === 0 && { gridColumnStart: 2 }) }}
|
||||
>
|
||||
<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>
|
||||
<Select
|
||||
defaultActiveFirstOption={false}
|
||||
placeholder="Select Operator"
|
||||
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 }}
|
||||
>
|
||||
{Object.values({
|
||||
...QueryOperatorsMultiVal,
|
||||
...QueryOperatorsSingleVal,
|
||||
}).map((cond) => (
|
||||
<Option key={cond} value={cond} label={cond} />
|
||||
<Option key={cond} value={cond} label={cond}>
|
||||
{cond}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
<div style={{ flex: 2 }}>
|
||||
{Array.isArray(query[2].value) ||
|
||||
Object.values(QueryOperatorsMultiVal).some(
|
||||
(op) => op.toUpperCase() === query[1].value?.toUpperCase(),
|
||||
) ? (
|
||||
Object.values(QueryOperatorsMultiVal).some(
|
||||
(op) => op.toUpperCase() === (query[1].value as string)?.toUpperCase(),
|
||||
) ? (
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{ width: '100%' }}
|
||||
onChange={(e) => handleChange(2, e)}
|
||||
onChange={(e): void => handleChange(2, e as never)}
|
||||
defaultValue={(query[2] && query[2].value) || []}
|
||||
notFoundContent={null}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
onChange={(e) => handleChange(2, e.target.value)}
|
||||
onChange={(e): void => handleChange(2, e.target.value)}
|
||||
style={{ width: '100%' }}
|
||||
defaultValue={query[2] && query[2].value}
|
||||
/>
|
||||
@ -120,24 +134,39 @@ function QueryField({ query, queryIndex, onUpdate, onDelete }) {
|
||||
</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 (
|
||||
<QueryConditionContainer>
|
||||
<Select
|
||||
defaultValue={query.value.toUpperCase()}
|
||||
onChange={(e) => {
|
||||
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} />
|
||||
<Option key={cond} value={cond} label={cond}>
|
||||
{cond}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</QueryConditionContainer>
|
||||
);
|
||||
}
|
||||
const hashCode = (s) => {
|
||||
const hashCode = (s: string): string => {
|
||||
if (!s) {
|
||||
return '0';
|
||||
}
|
||||
@ -149,14 +178,20 @@ const hashCode = (s) => {
|
||||
)}`;
|
||||
};
|
||||
|
||||
function QueryBuilder({ updateParsedQuery }) {
|
||||
function QueryBuilder({
|
||||
updateParsedQuery,
|
||||
}: {
|
||||
updateParsedQuery: (arg0: unknown) => void;
|
||||
}): JSX.Element {
|
||||
const {
|
||||
searchFilter: { parsedQuery },
|
||||
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
||||
|
||||
const keyPrefixRef = useRef(hashCode(JSON.stringify(parsedQuery)));
|
||||
const [keyPrefix, setKeyPrefix] = useState(keyPrefixRef.current);
|
||||
const generatedQueryStructure = createParsedQueryStructure(parsedQuery);
|
||||
const generatedQueryStructure = createParsedQueryStructure(
|
||||
parsedQuery as never[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const incomingHashCode = hashCode(JSON.stringify(parsedQuery));
|
||||
@ -166,18 +201,19 @@ function QueryBuilder({ updateParsedQuery }) {
|
||||
}
|
||||
}, [parsedQuery]);
|
||||
|
||||
|
||||
|
||||
const handleUpdate = (query, queryIndex): void => {
|
||||
const handleUpdate = (
|
||||
query: { value: string | string[]; type: string }[],
|
||||
queryIndex: number,
|
||||
): void => {
|
||||
const updatedParsedQuery = generatedQueryStructure;
|
||||
updatedParsedQuery[queryIndex] = query;
|
||||
updatedParsedQuery[queryIndex] = query as never;
|
||||
|
||||
const flatParsedQuery = flatten(updatedParsedQuery).filter((q) => q.value);
|
||||
keyPrefixRef.current = hashCode(JSON.stringify(flatParsedQuery));
|
||||
updateParsedQuery(flatParsedQuery);
|
||||
};
|
||||
|
||||
const handleDelete = (queryIndex) => {
|
||||
const handleDelete = (queryIndex: number): void => {
|
||||
const updatedParsedQuery = generatedQueryStructure;
|
||||
updatedParsedQuery.splice(queryIndex - 1, 2);
|
||||
|
||||
@ -186,15 +222,15 @@ function QueryBuilder({ updateParsedQuery }) {
|
||||
updateParsedQuery(flatParsedQuery);
|
||||
};
|
||||
|
||||
const QueryUI = () =>
|
||||
const QueryUI = (): JSX.Element | JSX.Element[] =>
|
||||
generatedQueryStructure.map((query, idx) => {
|
||||
if (Array.isArray(query))
|
||||
return (
|
||||
<QueryField
|
||||
key={keyPrefix + idx}
|
||||
query={query}
|
||||
query={query as never}
|
||||
queryIndex={idx}
|
||||
onUpdate={handleUpdate}
|
||||
onUpdate={handleUpdate as never}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
);
|
||||
@ -204,7 +240,7 @@ function QueryBuilder({ updateParsedQuery }) {
|
||||
key={keyPrefix + idx}
|
||||
query={query}
|
||||
queryIndex={idx}
|
||||
onUpdate={handleUpdate}
|
||||
onUpdate={handleUpdate as never}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -1,18 +1,22 @@
|
||||
import { Button, Typography } from 'antd';
|
||||
import { Button } from 'antd';
|
||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||
import { map } from 'lodash-es';
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
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';
|
||||
|
||||
function SuggestedItem({ name, type }) {
|
||||
interface SuggestedItemProps {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
function SuggestedItem({ name, type }: SuggestedItemProps): JSX.Element {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const addSuggestedField = () => {
|
||||
const addSuggestedField = (): void => {
|
||||
dispatch({
|
||||
type: ADD_SEARCH_FIELD_QUERY_STRING,
|
||||
payload: name,
|
||||
@ -29,7 +33,7 @@ function SuggestedItem({ name, type }) {
|
||||
);
|
||||
}
|
||||
|
||||
function Suggestions() {
|
||||
function Suggestions(): JSX.Element {
|
||||
const {
|
||||
fields: { selected },
|
||||
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
||||
|
@ -1,9 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
import QueryBuilder from './QueryBuilder/QueryBuilder';
|
||||
import Suggestions from './Suggestions';
|
||||
|
||||
function SearchFields({updateParsedQuery}): JSX.Element {
|
||||
|
||||
interface SearchFieldsProps {
|
||||
updateParsedQuery: () => void;
|
||||
}
|
||||
function SearchFields({ updateParsedQuery }: SearchFieldsProps): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<QueryBuilder updateParsedQuery={updateParsedQuery} />
|
||||
|
@ -1,3 +1,7 @@
|
||||
/* eslint-disable */
|
||||
// @ts-ignore
|
||||
// @ts-nocheck
|
||||
|
||||
import { QueryTypes } from 'lib/logql/tokens';
|
||||
|
||||
export const queryKOVPair = () => [
|
||||
@ -30,7 +34,7 @@ export const createParsedQueryStructure = (parsedQuery = []) => {
|
||||
cond = null;
|
||||
qCtr = -1;
|
||||
}
|
||||
let stagingArr = structuredArray[structuredArray.length - 1];
|
||||
const stagingArr = structuredArray[structuredArray.length - 1];
|
||||
const prevQuery =
|
||||
Array.isArray(stagingArr) && qCtr >= 0 ? stagingArr[qCtr] : null;
|
||||
|
||||
|
@ -1,23 +1,11 @@
|
||||
import {
|
||||
CloseCircleFilled,
|
||||
CloseCircleOutlined,
|
||||
CloseSquareOutlined,
|
||||
} from '@ant-design/icons';
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { CloseSquareOutlined } from '@ant-design/icons';
|
||||
import { Button, Input } from 'antd';
|
||||
import useClickOutside from 'hooks/useClickOutside';
|
||||
import getStep from 'lib/getStep';
|
||||
import { debounce, throttle } from 'lodash-es';
|
||||
import React, {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { useClickAway, useLocation } from 'react-use';
|
||||
import { useLocation } from 'react-use';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { getLogs } from 'store/actions/logs/getLogs';
|
||||
@ -26,7 +14,7 @@ import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { TOGGLE_LIVE_TAIL } from 'types/actions/logs';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import ILogsReducer from 'types/reducer/logs';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import SearchFields from './SearchFields';
|
||||
import { DropDownContainer } from './styles';
|
||||
@ -34,7 +22,16 @@ import { useSearchParser } from './useSearchParser';
|
||||
|
||||
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 {
|
||||
queryString,
|
||||
updateParsedQuery,
|
||||
@ -146,26 +143,29 @@ function SearchFilter({ getLogs, getLogsAggregate }) {
|
||||
<DropDownContainer>
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => setShowDropDown(false)}
|
||||
onClick={(): void => setShowDropDown(false)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
}}
|
||||
>
|
||||
<CloseSquareOutlined size="large" />
|
||||
<CloseSquareOutlined />
|
||||
</Button>
|
||||
<SearchFields updateParsedQuery={updateParsedQuery} />
|
||||
<SearchFields updateParsedQuery={updateParsedQuery as never} />
|
||||
</DropDownContainer>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getLogs: () => (dispatch: Dispatch<AppActions>) => void;
|
||||
getLogsAggregate: () => (dispatch: Dispatch<AppActions>) => void;
|
||||
getLogs: (
|
||||
props: Parameters<typeof getLogs>[0],
|
||||
) => (dispatch: Dispatch<AppActions>) => void;
|
||||
getLogsAggregate: (
|
||||
props: Parameters<typeof getLogsAggregate>[0],
|
||||
) => (dispatch: Dispatch<AppActions>) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
|
@ -1,16 +1,21 @@
|
||||
import history from 'lib/history';
|
||||
import { parseQuery, reverseParser } from 'lib/logql';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { useCallback, useEffect, useReducer, useState } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import {
|
||||
SET_SEARCH_QUERY_PARSED_PAYLOAD,
|
||||
SET_SEARCH_QUERY_STRING,
|
||||
} 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 {
|
||||
searchFilter: { parsedQuery, queryString },
|
||||
|
@ -1,23 +1,24 @@
|
||||
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
||||
import { Card, Typography } from 'antd';
|
||||
import { LiveTail } from 'api/logs/livetail';
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { Typography } from 'antd';
|
||||
import LogItem from 'components/Logs/LogItem';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { map } from 'lodash-es';
|
||||
import React, { memo, useEffect, useRef } from 'react';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
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 { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { PUSH_LIVE_TAIL_EVENT } from 'types/actions/logs';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import ILogsReducer from 'types/reducer/logs';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
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 {
|
||||
searchFilter: { queryString },
|
||||
logs,
|
||||
@ -51,7 +52,7 @@ function LogsTable({ getLogs }) {
|
||||
return <Spinner height={20} tip="Getting Logs" />;
|
||||
}
|
||||
return (
|
||||
<Container>
|
||||
<Container flex="auto">
|
||||
<Heading>
|
||||
<Typography.Text
|
||||
style={{
|
||||
@ -74,7 +75,9 @@ function LogsTable({ getLogs }) {
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getLogs: () => (dispatch: Dispatch<AppActions>) => void;
|
||||
getLogs: (
|
||||
props: Parameters<typeof getLogs>[0],
|
||||
) => (dispatch: Dispatch<AppActions>) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { grey } from '@ant-design/colors';
|
||||
import { Card, Col } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
@ -9,7 +8,6 @@ export const Container = styled(Col)`
|
||||
`;
|
||||
|
||||
export const Heading = styled(Card)`
|
||||
|
||||
margin-bottom: 0.1rem;
|
||||
.ant-card-body {
|
||||
padding: 0.3rem 0.5rem;
|
||||
|
@ -49,7 +49,8 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
|
||||
|
||||
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`,
|
||||
);
|
||||
};
|
||||
@ -100,17 +101,12 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
|
||||
|
||||
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`,
|
||||
);
|
||||
};
|
||||
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 (
|
||||
<>
|
||||
<Row gutter={24}>
|
||||
|
@ -18,7 +18,7 @@ const breadcrumbNameMap = {
|
||||
[ROUTES.ERROR_DETAIL]: 'Errors',
|
||||
[ROUTES.LIST_ALL_ALERT]: 'Alerts',
|
||||
[ROUTES.ALL_DASHBOARD]: 'Dashboard',
|
||||
[ROUTES.LOGS]: 'Logs'
|
||||
[ROUTES.LOGS]: 'Logs',
|
||||
};
|
||||
|
||||
function ShowBreadcrumbs(props: RouteComponentProps): JSX.Element {
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
/* eslint no-useless-escape: 0 */
|
||||
|
||||
const logqlQueries = [
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { logqlQueries } from 'lib/__fixtures__/logql';
|
||||
import reverseParser from 'lib/logql/reverseParser';
|
||||
import { reverseParser } from 'lib/logql/reverseParser';
|
||||
|
||||
describe('lib/logql/reverseParser', () => {
|
||||
test('reverse parse valid queries', () => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import splitter from 'lib/logql/splitter';
|
||||
import { logqlQueries } from 'lib/__fixtures__/logql';
|
||||
import { splitter } from 'lib/logql/splitter';
|
||||
|
||||
describe('lib/logql/splitter', () => {
|
||||
test('splitter valid quereies', () => {
|
||||
logqlQueries.forEach((queryObject) => {
|
||||
|
@ -1,4 +1,11 @@
|
||||
/* eslint-disable */
|
||||
// @ts-ignore
|
||||
// @ts-nocheck
|
||||
|
||||
import {
|
||||
ErrorConvertToFullText,
|
||||
ErrorInvalidQueryPair,
|
||||
} from 'lib/logql/errors';
|
||||
import splitter from 'lib/logql/splitter';
|
||||
import {
|
||||
ConditionalOperators,
|
||||
@ -6,10 +13,6 @@ import {
|
||||
QueryOperatorsSingleVal,
|
||||
QueryTypes,
|
||||
} from 'lib/logql/tokens';
|
||||
import {
|
||||
ErrorConvertToFullText,
|
||||
ErrorInvalidQueryPair,
|
||||
} from 'lib/logql/errors';
|
||||
|
||||
const validateMultiValue = (queryToken: string): boolean => {
|
||||
const queryValues = [];
|
||||
|
@ -1,3 +1,5 @@
|
||||
/* eslint-disable */
|
||||
// @ts-ignore
|
||||
// @ts-nocheck
|
||||
|
||||
export const reverseParser = (
|
||||
|
@ -1,7 +1,9 @@
|
||||
/* eslint-disable */
|
||||
// @ts-ignore
|
||||
// @ts-nocheck
|
||||
|
||||
export const splitter = (queryString) => {
|
||||
const splittedParts = [];
|
||||
export const splitter = (queryString: string): string[] => {
|
||||
const splittedParts: string[] = [];
|
||||
let start = 0;
|
||||
let isBracketStart = false;
|
||||
let isQuoteStart = false;
|
||||
@ -13,10 +15,6 @@ export const splitter = (queryString) => {
|
||||
for (let idx = 0; idx < queryString.length; idx += 1) {
|
||||
const currentChar = queryString[idx];
|
||||
|
||||
// if (start === null) {
|
||||
// start = idx;
|
||||
// }
|
||||
|
||||
if (currentChar === ' ') {
|
||||
if (!isBracketStart && !isQuoteStart) {
|
||||
pushPart(idx);
|
||||
@ -48,7 +46,6 @@ export const splitter = (queryString) => {
|
||||
pushPart(queryString.length);
|
||||
}
|
||||
|
||||
// console.log(splittedParts.filter(Boolean));
|
||||
return splittedParts.map((s) => String.raw`${s}`).filter(Boolean);
|
||||
};
|
||||
|
||||
|
@ -17,7 +17,6 @@ export const ConditionalOperators = {
|
||||
OR: 'OR',
|
||||
};
|
||||
|
||||
|
||||
export const QueryTypes = {
|
||||
QUERY_KEY: 'QUERY_KEY',
|
||||
QUERY_OPERATOR: 'QUERY_OPERATOR',
|
||||
|
@ -1,4 +1,7 @@
|
||||
export const fieldSearchFilter = (searchSpace = '', currentValue = '') => {
|
||||
export const fieldSearchFilter = (
|
||||
searchSpace = '',
|
||||
currentValue = '',
|
||||
): boolean => {
|
||||
if (!currentValue || !searchSpace) {
|
||||
return true;
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
export function FlatLogData(log: ILog): Record<string, never> {
|
||||
let flattenLogObject: Record<string, never> = {};
|
||||
export function FlatLogData(log: ILog): Record<string, unknown> {
|
||||
const flattenLogObject: Record<string, unknown> = {};
|
||||
|
||||
Object.keys(log).forEach((key: string) => {
|
||||
if (typeof log[key] !== 'object') {
|
||||
flattenLogObject[key] = log[key];
|
||||
Object.keys(log).forEach((key: string): void => {
|
||||
if (typeof log[key as never] !== 'object') {
|
||||
flattenLogObject[key] = log[key as never];
|
||||
} else {
|
||||
Object.keys(log[key]).forEach((childKey) => {
|
||||
flattenLogObject[childKey] = log[key][childKey]
|
||||
Object.keys(log[key as never]).forEach((childKey) => {
|
||||
flattenLogObject[childKey] = log[key as never][childKey];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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} `;
|
||||
if (typeof fieldValue === 'number') {
|
||||
generatedQueryString += `(${fieldValue})`;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Logs from 'container/Logs';
|
||||
import React from 'react';
|
||||
|
||||
function LogsHome() {
|
||||
function LogsHome(): JSX.Element {
|
||||
return <Logs />;
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,17 @@
|
||||
import GetSearchFields from 'api/logs/GetSearchFields';
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { GET_FIELDS, SET_FIELDS } from 'types/actions/logs';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { SET_FIELDS } from 'types/actions/logs';
|
||||
|
||||
export const AddToSelectedField = (): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return async (dispatch): void => {
|
||||
export const AddToSelectedField = (): ((
|
||||
dispatch: Dispatch<AppActions>,
|
||||
) => void) => {
|
||||
return async (dispatch): Promise<void> => {
|
||||
const response = await GetSearchFields();
|
||||
|
||||
dispatch({
|
||||
type: SET_FIELDS,
|
||||
payload: response.payload,
|
||||
});
|
||||
if (response.payload)
|
||||
dispatch({
|
||||
type: SET_FIELDS,
|
||||
payload: response.payload,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
@ -1,13 +1,12 @@
|
||||
import GetSearchFields from 'api/logs/GetSearchFields';
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { GET_FIELDS, SET_FIELDS } from 'types/actions/logs';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { SET_FIELDS } from 'types/actions/logs';
|
||||
|
||||
const IGNORED_SELECTED_FIELDS = ['timestamp'];
|
||||
|
||||
export const GetLogsFields = (): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return async (dispatch): void => {
|
||||
return async (dispatch): Promise<void> => {
|
||||
const response = await GetSearchFields();
|
||||
if (response.payload) {
|
||||
dispatch({
|
||||
|
@ -2,9 +2,12 @@ import GetLogs from 'api/logs/GetLogs';
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { SET_LOADING, SET_LOGS } from 'types/actions/logs';
|
||||
import { Props } from 'types/api/logs/getLogs';
|
||||
|
||||
export const getLogs = (props): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return async (dispatch): void => {
|
||||
export const getLogs = (
|
||||
props: Props,
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return async (dispatch): Promise<void> => {
|
||||
dispatch({
|
||||
type: SET_LOADING,
|
||||
payload: true,
|
||||
|
@ -1,19 +1,17 @@
|
||||
import GetLogs from 'api/logs/GetLogs';
|
||||
import GetLogsAggregate from 'api/logs/GetLogsAggregate';
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
SET_LOADING,
|
||||
SET_LOADING_AGGREGATE,
|
||||
SET_LOGS,
|
||||
SET_LOGS_AGGREGATE_SERIES,
|
||||
} from 'types/actions/logs';
|
||||
import { Props } from 'types/api/logs/getLogsAggregate';
|
||||
import { ILogsAggregate } from 'types/api/logs/logAggregate';
|
||||
|
||||
export const getLogsAggregate = (
|
||||
props,
|
||||
props: Props,
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return async (dispatch): void => {
|
||||
return async (dispatch): Promise<void> => {
|
||||
dispatch({
|
||||
type: SET_LOADING_AGGREGATE,
|
||||
payload: true,
|
||||
|
@ -3,7 +3,7 @@ import { combineReducers } from 'redux';
|
||||
import appReducer from './app';
|
||||
import dashboardReducer from './dashboard';
|
||||
import globalTimeReducer from './global';
|
||||
import LogsReducer from './logs';
|
||||
import { LogsReducer } from './logs';
|
||||
import metricsReducers from './metric';
|
||||
import { ServiceMapReducer } from './serviceMap';
|
||||
import traceReducer from './trace';
|
||||
|
@ -21,7 +21,7 @@ import {
|
||||
STOP_LIVE_TAIL,
|
||||
TOGGLE_LIVE_TAIL,
|
||||
} from 'types/actions/logs';
|
||||
import ILogsReducer from 'types/reducer/logs';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
const initialState: ILogsReducer = {
|
||||
fields: {
|
||||
@ -39,33 +39,10 @@ const initialState: ILogsReducer = {
|
||||
isLoading: false,
|
||||
isLoadingAggregate: false,
|
||||
logsAggregate: [],
|
||||
detailedLog: null,
|
||||
liveTail: 'STOPPED',
|
||||
liveTailStartRange: 15,
|
||||
// detailedLog: {
|
||||
// timestamp: 1659360016955270100,
|
||||
// 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: {},
|
||||
// },
|
||||
selectedLogId: null,
|
||||
detailedLog: null,
|
||||
};
|
||||
|
||||
export const LogsReducer = (
|
||||
@ -123,9 +100,12 @@ export const LogsReducer = (
|
||||
|
||||
case ADD_SEARCH_FIELD_QUERY_STRING: {
|
||||
const updatedQueryString =
|
||||
state.searchFilter.queryString +
|
||||
(state.searchFilter.queryString.length > 0 ? ' and ' : '') +
|
||||
action.payload;
|
||||
state.searchFilter.queryString ||
|
||||
`${
|
||||
state.searchFilter.queryString && state.searchFilter.queryString.length > 0
|
||||
? ' and '
|
||||
: ''
|
||||
}${action.payload}`;
|
||||
|
||||
const updatedParsedQuery = parseQuery(updatedQueryString);
|
||||
return {
|
||||
|
@ -123,42 +123,6 @@ export interface SetLiveTailStartTime {
|
||||
type: typeof SET_LIVE_TAIL_START_TIME;
|
||||
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 =
|
||||
| GetFields
|
||||
|
@ -6,4 +6,8 @@ export type Props = {
|
||||
limit: number;
|
||||
orderBy: string;
|
||||
order: string;
|
||||
idGt?: string;
|
||||
idLt?: string;
|
||||
timestampStart?: number;
|
||||
timestampEnd?: number;
|
||||
};
|
||||
|
@ -9,4 +9,7 @@ export type Props = {
|
||||
timestampStart: number;
|
||||
timestampEnd: number;
|
||||
step: number;
|
||||
q?: string;
|
||||
idGt?: string;
|
||||
idLt?: string;
|
||||
};
|
||||
|
@ -17,7 +17,7 @@ export interface ILogsReducer {
|
||||
isLoading: boolean;
|
||||
isLoadingAggregate: boolean;
|
||||
logsAggregate: ILogsAggregate[];
|
||||
selectedLogId: string;
|
||||
selectedLogId: string | null;
|
||||
detailedLog: null | ILog;
|
||||
liveTail: TLogsLiveTailState;
|
||||
liveTailStartRange: number; // time in minutes
|
||||
|
3568
frontend/yarn.lock
3568
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user