mirror of
https://github.com/SigNoz/signoz.git
synced 2026-09-19 18:00:41 +01:00
Compare commits
2 Commits
fix/qf-set
...
removal
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3de6f2e72c | ||
|
|
be9170ced6 |
@@ -43,7 +43,6 @@
|
||||
"NOT_FOUND": "SigNoz | Page Not Found",
|
||||
"LOGS": "SigNoz | Logs",
|
||||
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
|
||||
"OLD_LOGS_EXPLORER": "SigNoz | Old Logs Explorer",
|
||||
"LIVE_LOGS": "SigNoz | Live Logs",
|
||||
"LOGS_PIPELINES": "SigNoz | Logs Pipelines",
|
||||
"HOME_PAGE": "Open source Observability Platform | SigNoz",
|
||||
|
||||
@@ -1602,10 +1602,6 @@ describe('PrivateRoute', () => {
|
||||
LOGS: { path: ROUTES.LOGS, deniedRoles: DENIED_ROLES },
|
||||
LOGS_EXPLORER: { path: ROUTES.LOGS_EXPLORER, deniedRoles: DENIED_ROLES },
|
||||
LIVE_LOGS: { path: ROUTES.LIVE_LOGS, deniedRoles: DENIED_ROLES },
|
||||
OLD_LOGS_EXPLORER: {
|
||||
path: ROUTES.OLD_LOGS_EXPLORER,
|
||||
deniedRoles: DENIED_ROLES,
|
||||
},
|
||||
METRICS_EXPLORER: {
|
||||
path: ROUTES.METRICS_EXPLORER,
|
||||
deniedRoles: DENIED_ROLES,
|
||||
|
||||
@@ -169,10 +169,6 @@ export const LogsExplorer = Loadable(
|
||||
() => import(/* webpackChunkName: "Logs Explorer" */ 'pages/LogsModulePage'),
|
||||
);
|
||||
|
||||
export const OldLogsExplorer = Loadable(
|
||||
() => import(/* webpackChunkName: "Logs Explorer" */ 'pages/Logs'),
|
||||
);
|
||||
|
||||
export const LiveLogs = Loadable(
|
||||
() => import(/* webpackChunkName: "Live Logs" */ 'pages/LiveLogs'),
|
||||
);
|
||||
|
||||
@@ -32,7 +32,6 @@ import {
|
||||
MessagingQueuesMainPage,
|
||||
MeterExplorerPage,
|
||||
MetricsExplorer,
|
||||
OldLogsExplorer,
|
||||
OnboardingV2,
|
||||
OrgOnboarding,
|
||||
PasswordReset,
|
||||
@@ -308,13 +307,6 @@ const routes: AppRoutes[] = [
|
||||
key: 'LOGS_EXPLORER',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.OLD_LOGS_EXPLORER,
|
||||
exact: true,
|
||||
component: OldLogsExplorer,
|
||||
key: 'OLD_LOGS_EXPLORER',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.LIVE_LOGS,
|
||||
exact: true,
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/logs/addToSelectedFields';
|
||||
|
||||
const addToSelectedFields = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const data = await axios.post(`/logs/fields`, props);
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: '',
|
||||
payload: data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return Promise.reject(ErrorResponseHandler(error as AxiosError));
|
||||
}
|
||||
};
|
||||
|
||||
export default addToSelectedFields;
|
||||
@@ -1,26 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/logs/getLogs';
|
||||
|
||||
const GetLogs = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const data = await axios.get(`/logs`, {
|
||||
params: props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: '',
|
||||
payload: data.data.results,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default GetLogs;
|
||||
@@ -1,26 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/logs/getLogsAggregate';
|
||||
|
||||
const GetLogsAggregate = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const data = await axios.get(`/logs/aggregate`, {
|
||||
params: props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: '',
|
||||
payload: data.data.items,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default GetLogsAggregate;
|
||||
@@ -1,24 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/logs/getSearchFields';
|
||||
|
||||
const GetSearchFields = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const data = await axios.get(`/logs/fields`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: '',
|
||||
payload: data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default GetSearchFields;
|
||||
@@ -1,23 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/logs/addToSelectedFields';
|
||||
|
||||
const removeSelectedField = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const data = await axios.post(`/logs/fields`, props);
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: '',
|
||||
payload: data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return Promise.reject(ErrorResponseHandler(error as AxiosError));
|
||||
}
|
||||
};
|
||||
|
||||
export default removeSelectedField;
|
||||
@@ -1,22 +0,0 @@
|
||||
import apiV1 from 'api/apiV1';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
// 10 min in ms
|
||||
const TIMEOUT_IN_MS = 10 * 60 * 1000;
|
||||
|
||||
export const LiveTail = (queryParams: string): EventSourcePolyfill =>
|
||||
new EventSourcePolyfill(
|
||||
ENVIRONMENT.baseURL
|
||||
? `${ENVIRONMENT.baseURL}${apiV1}logs/tail?${queryParams}`
|
||||
: withBasePath(`${apiV1}logs/tail?${queryParams}`),
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${getLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN)}`,
|
||||
},
|
||||
heartbeatTimeout: TIMEOUT_IN_MS,
|
||||
},
|
||||
);
|
||||
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import { Button, InputNumber, Popover, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
import { LogViewMode } from 'container/OptionsMenu/types';
|
||||
import { FontSize, OptionsMenuConfig } from 'container/OptionsMenu/types';
|
||||
import {
|
||||
Check,
|
||||
|
||||
@@ -38,7 +38,6 @@ const ROUTES = {
|
||||
NOT_FOUND: '/not-found',
|
||||
LOGS_BASE: '/logs',
|
||||
LOGS: '/logs/logs-explorer',
|
||||
OLD_LOGS_EXPLORER: '/logs/old-logs-explorer',
|
||||
LOGS_EXPLORER: '/logs/logs-explorer',
|
||||
LIVE_LOGS: '/logs/logs-explorer/live',
|
||||
LOGS_PIPELINES: '/logs/pipelines',
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import { memo, useMemo } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Button, Flex } from 'antd';
|
||||
import { Divider } from '@signozhq/ui/divider';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import Controls from 'container/Controls';
|
||||
import Download from 'container/Download/Download';
|
||||
import { getGlobalTime } from 'container/LogsSearchFilter/utils';
|
||||
import dayjs from 'dayjs';
|
||||
import { Pagination } from 'hooks/queryPagination';
|
||||
import { getMinMaxForSelectedTime } from 'lib/getMinMax';
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { OrderPreferenceItems } from 'pages/Logs/config';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
GET_NEXT_LOG_LINES,
|
||||
GET_PREVIOUS_LOG_LINES,
|
||||
RESET_ID_START_AND_END,
|
||||
SET_LOG_LINES_PER_PAGE,
|
||||
} from 'types/actions/logs';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import { Container } from './styles';
|
||||
import { SkipBack } from '@signozhq/icons';
|
||||
|
||||
function LogControls(): JSX.Element | null {
|
||||
const {
|
||||
logLinesPerPage,
|
||||
liveTail,
|
||||
isLoading: isLogsLoading,
|
||||
isLoadingAggregate,
|
||||
logs,
|
||||
order,
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const handleLogLinesPerPageChange = (e: Pagination['limit']): void => {
|
||||
dispatch({
|
||||
type: SET_LOG_LINES_PER_PAGE,
|
||||
payload: {
|
||||
logsLinesPerPage: e,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleGoToLatest = (): void => {
|
||||
const { maxTime, minTime } = getMinMaxForSelectedTime(
|
||||
globalTime.selectedTime,
|
||||
globalTime.minTime,
|
||||
globalTime.maxTime,
|
||||
);
|
||||
|
||||
const updatedGlobalTime = getGlobalTime(globalTime.selectedTime, {
|
||||
maxTime,
|
||||
minTime,
|
||||
});
|
||||
|
||||
if (updatedGlobalTime) {
|
||||
dispatch({
|
||||
type: RESET_ID_START_AND_END,
|
||||
payload: updatedGlobalTime,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleNavigatePrevious = (): void => {
|
||||
dispatch({
|
||||
type: GET_PREVIOUS_LOG_LINES,
|
||||
});
|
||||
};
|
||||
|
||||
const handleNavigateNext = (): void => {
|
||||
dispatch({
|
||||
type: GET_NEXT_LOG_LINES,
|
||||
});
|
||||
};
|
||||
|
||||
const flattenLogData = useMemo(
|
||||
() =>
|
||||
logs.map((log) => {
|
||||
const timestamp =
|
||||
typeof log.timestamp === 'string'
|
||||
? dayjs(log.timestamp).format(DATE_TIME_FORMATS.ISO_DATETIME_MS)
|
||||
: dayjs(log.timestamp / 1e6).format(DATE_TIME_FORMATS.ISO_DATETIME_MS);
|
||||
|
||||
return FlatLogData({
|
||||
...log,
|
||||
timestamp,
|
||||
});
|
||||
}),
|
||||
[logs],
|
||||
);
|
||||
|
||||
const isLoading = isLogsLoading || isLoadingAggregate;
|
||||
|
||||
if (liveTail !== 'STOPPED') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Download data={flattenLogData} isLoading={isLoading} fileName="log_data" />
|
||||
<Button
|
||||
loading={isLoading}
|
||||
size="small"
|
||||
type="link"
|
||||
disabled={order === OrderPreferenceItems.ASC}
|
||||
onClick={handleGoToLatest}
|
||||
>
|
||||
<Flex align="center" gap="4px">
|
||||
<SkipBack size="md" /> Go to latest
|
||||
</Flex>
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Controls
|
||||
isLoading={isLoading}
|
||||
totalCount={logs.length}
|
||||
countPerPage={logLinesPerPage}
|
||||
handleNavigatePrevious={handleNavigatePrevious}
|
||||
handleNavigateNext={handleNavigateNext}
|
||||
handleCountItemsPerPageChange={handleLogLinesPerPageChange}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(LogControls);
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Button } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
`;
|
||||
|
||||
export const DownloadLogButton = styled(Button)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
@@ -1,6 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { Link, Pin } from '@signozhq/icons';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
@@ -14,23 +12,19 @@ import { ResizeTable } from 'components/ResizeTable';
|
||||
import { OPERATORS } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { RESTRICTED_SELECTED_FIELDS } from 'container/LogsFilters/config';
|
||||
import { MetricsType } from 'container/MetricsApplication/constant';
|
||||
import { FontSize, OptionsQuery } from 'container/OptionsMenu/types';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import history from 'lib/history';
|
||||
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
||||
import { removeJSONStringifyQuotes } from 'lib/removeJSONStringifyQuotes';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import { ActionItemProps } from './ActionItem';
|
||||
import { RESTRICTED_SELECTED_FIELDS } from './config';
|
||||
import FieldRenderer from './FieldRenderer';
|
||||
import TableViewActions from './TableView/TableViewActions';
|
||||
import {
|
||||
@@ -65,7 +59,6 @@ function TableView({
|
||||
listViewPanelSelectedFields,
|
||||
handleChangeSelectedView,
|
||||
}: Props): JSX.Element | null {
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
const [isfilterInLoading, setIsFilterInLoading] = useState<boolean>(false);
|
||||
const [isfilterOutLoading, setIsFilterOutLoading] = useState<boolean>(false);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
@@ -185,11 +178,6 @@ function TableView({
|
||||
const spanId = flattenLogData?.span_id;
|
||||
|
||||
if (traceId) {
|
||||
dispatch({
|
||||
type: SET_DETAILED_LOG_DATA,
|
||||
payload: null,
|
||||
});
|
||||
|
||||
const basePath = generatePath(ROUTES.TRACE_DETAIL, {
|
||||
id: traceId,
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@ import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { OPERATORS } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { RESTRICTED_SELECTED_FIELDS } from 'container/LogsFilters/config';
|
||||
import { MetricsType } from 'container/MetricsApplication/constant';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { ICurrentQueryData } from 'hooks/useHandleExplorerTabChange';
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
DataTypes,
|
||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
|
||||
import { RESTRICTED_SELECTED_FIELDS } from '../config';
|
||||
import { DataType } from '../TableView';
|
||||
import {
|
||||
filterKeyForField,
|
||||
@@ -141,10 +141,9 @@ export default function TableViewActions(
|
||||
const { stagedQuery, updateQueriesData } = useQueryBuilder();
|
||||
const { dataType, logType: fieldType } = getFieldAttributes(record.field);
|
||||
|
||||
// there is no option for where clause in old logs explorer and live logs page or infra monitoring
|
||||
const isOldLogsExplorerOrLiveLogsPage = useMemo(
|
||||
// there is no option for where clause in live logs page or infra monitoring
|
||||
const isLiveLogsOrInfraPage = useMemo(
|
||||
() =>
|
||||
pathname === ROUTES.OLD_LOGS_EXPLORER ||
|
||||
pathname === ROUTES.LIVE_LOGS ||
|
||||
pathname === ROUTES.INFRASTRUCTURE_MONITORING_HOSTS ||
|
||||
pathname === ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES,
|
||||
@@ -400,7 +399,7 @@ export default function TableViewActions(
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
{!isOldLogsExplorerOrLiveLogsPage && (
|
||||
{!isLiveLogsOrInfraPage && (
|
||||
<Popover
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
@@ -487,7 +486,7 @@ export default function TableViewActions(
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
{!isOldLogsExplorerOrLiveLogsPage && (
|
||||
{!isLiveLogsOrInfraPage && (
|
||||
<Popover
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { RESTRICTED_SELECTED_FIELDS } from 'container/LogsFilters/config';
|
||||
import { useGetSavedViewParams } from 'hooks/saveViews/useGetSavedViewParams';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { ExplorerViews } from 'pages/LogsExplorer/utils';
|
||||
|
||||
import { RESTRICTED_SELECTED_FIELDS } from '../../config';
|
||||
import TableViewActions from '../TableViewActions';
|
||||
import useAsyncJSONProcessing from '../useAsyncJSONProcessing';
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
|
||||
export const RESTRICTED_SELECTED_FIELDS = ['timestamp', 'id'];
|
||||
|
||||
// Fields that can be filtered on but not grouped by in the log details view.
|
||||
export const RESTRICTED_GROUP_BY_FIELDS = ['body', 'trace_id'];
|
||||
|
||||
export const typeToArrayTypeMapper: { [key in DataTypes]: DataTypes } = {
|
||||
[DataTypes.String]: DataTypes.ArrayString,
|
||||
[DataTypes.Float64]: DataTypes.ArrayFloat64,
|
||||
|
||||
@@ -61,8 +61,7 @@ export function useLogAttributeActions({
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.USE_JSON_BODY)
|
||||
?.active || false;
|
||||
|
||||
const isOldExplorerOrLive =
|
||||
pathname === ROUTES.OLD_LOGS_EXPLORER || pathname === ROUTES.LIVE_LOGS;
|
||||
const isLiveLogs = pathname === ROUTES.LIVE_LOGS;
|
||||
|
||||
const filterFor = useCallback(
|
||||
(context: FieldContext, isFilterIn: boolean): void => {
|
||||
@@ -221,7 +220,7 @@ export function useLogAttributeActions({
|
||||
!handleChangeSelectedView ||
|
||||
!buildLogFilterTarget(fieldKeyPath, undefined, isBodyJsonQueryEnabled)
|
||||
.groupBySupported ||
|
||||
isOldExplorerOrLive,
|
||||
isLiveLogs,
|
||||
},
|
||||
{
|
||||
key: LogDetailsAction.REPLACE_FILTER,
|
||||
@@ -229,9 +228,7 @@ export function useLogAttributeActions({
|
||||
icon: <RefreshCw size={12} />,
|
||||
onClick: replaceFilter,
|
||||
shouldHide: (_key, fieldKeyPath): boolean =>
|
||||
!handleChangeSelectedView ||
|
||||
isRestricted(fieldKeyPath) ||
|
||||
isOldExplorerOrLive,
|
||||
!handleChangeSelectedView || isRestricted(fieldKeyPath) || isLiveLogs,
|
||||
},
|
||||
];
|
||||
}, [
|
||||
@@ -239,7 +236,7 @@ export function useLogAttributeActions({
|
||||
groupBy,
|
||||
replaceFilter,
|
||||
isBodyJsonQueryEnabled,
|
||||
isOldExplorerOrLive,
|
||||
isLiveLogs,
|
||||
handleChangeSelectedView,
|
||||
onApplyLogFilter,
|
||||
]);
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
import { memo, useCallback } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { getOldLogsOperatorFromNew } from 'hooks/logs/useActiveLog';
|
||||
import { getGeneratedFilterQueryString } from 'lib/getGeneratedFilterQueryString';
|
||||
import getStep from 'lib/getStep';
|
||||
import { getIdConditions } from 'pages/Logs/utils';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { getLogs } from 'store/actions/logs/getLogs';
|
||||
import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
SET_DETAILED_LOG_DATA,
|
||||
SET_SEARCH_QUERY_STRING,
|
||||
TOGGLE_LIVE_TAIL,
|
||||
} from 'types/actions/logs';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
type LogDetailedViewProps = {
|
||||
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
|
||||
getLogsAggregate: (
|
||||
props: Parameters<typeof getLogsAggregate>[0],
|
||||
) => ReturnType<typeof getLogsAggregate>;
|
||||
};
|
||||
|
||||
function LogDetailedView({
|
||||
getLogs,
|
||||
getLogsAggregate,
|
||||
}: LogDetailedViewProps): JSX.Element {
|
||||
const history = useHistory();
|
||||
const {
|
||||
detailedLog,
|
||||
searchFilter: { queryString },
|
||||
logLinesPerPage,
|
||||
idStart,
|
||||
liveTail,
|
||||
idEnd,
|
||||
order,
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const onDrawerClose = (): void => {
|
||||
dispatch({
|
||||
type: SET_DETAILED_LOG_DATA,
|
||||
payload: null,
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddToQuery = useCallback(
|
||||
(fieldKey: string, fieldValue: string, operator: string) => {
|
||||
const newOperator = getOldLogsOperatorFromNew(operator);
|
||||
const updatedQueryString = getGeneratedFilterQueryString(
|
||||
fieldKey,
|
||||
fieldValue,
|
||||
newOperator,
|
||||
queryString,
|
||||
);
|
||||
|
||||
history.replace(`${ROUTES.OLD_LOGS_EXPLORER}?q=${updatedQueryString}`);
|
||||
},
|
||||
[history, queryString],
|
||||
);
|
||||
|
||||
const handleClickActionItem = useCallback(
|
||||
(fieldKey: string, fieldValue: string, operator: string): void => {
|
||||
const newOperator = getOldLogsOperatorFromNew(operator);
|
||||
const updatedQueryString = getGeneratedFilterQueryString(
|
||||
fieldKey,
|
||||
fieldValue,
|
||||
newOperator,
|
||||
queryString,
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: SET_SEARCH_QUERY_STRING,
|
||||
payload: {
|
||||
searchQueryString: updatedQueryString,
|
||||
},
|
||||
});
|
||||
|
||||
if (liveTail === 'STOPPED') {
|
||||
getLogs({
|
||||
q: updatedQueryString,
|
||||
limit: logLinesPerPage,
|
||||
orderBy: 'timestamp',
|
||||
order,
|
||||
timestampStart: minTime,
|
||||
timestampEnd: maxTime,
|
||||
...getIdConditions(idStart, idEnd, order),
|
||||
});
|
||||
getLogsAggregate({
|
||||
timestampStart: minTime,
|
||||
timestampEnd: maxTime,
|
||||
step: getStep({
|
||||
start: minTime,
|
||||
end: maxTime,
|
||||
inputFormat: 'ns',
|
||||
}),
|
||||
q: updatedQueryString,
|
||||
});
|
||||
} else if (liveTail === 'PLAYING') {
|
||||
dispatch({
|
||||
type: TOGGLE_LIVE_TAIL,
|
||||
payload: 'PAUSED',
|
||||
});
|
||||
setTimeout(
|
||||
() =>
|
||||
dispatch({
|
||||
type: TOGGLE_LIVE_TAIL,
|
||||
payload: liveTail,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
}
|
||||
},
|
||||
[
|
||||
dispatch,
|
||||
getLogs,
|
||||
getLogsAggregate,
|
||||
idEnd,
|
||||
idStart,
|
||||
liveTail,
|
||||
logLinesPerPage,
|
||||
maxTime,
|
||||
minTime,
|
||||
order,
|
||||
queryString,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<LogDetail
|
||||
selectedTab={VIEW_TYPES.OVERVIEW}
|
||||
log={detailedLog}
|
||||
onClose={onDrawerClose}
|
||||
onAddToQuery={handleAddToQuery}
|
||||
onClickActionItem={handleClickActionItem}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getLogs: (props: Parameters<typeof getLogs>[0]) => (dispatch: never) => void;
|
||||
getLogsAggregate: (
|
||||
props: Parameters<typeof getLogsAggregate>[0],
|
||||
) => (dispatch: never) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
getLogs: bindActionCreators(getLogs, dispatch),
|
||||
getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(memo(LogDetailedView as any));
|
||||
@@ -5,10 +5,6 @@ import {
|
||||
QUERY_BUILDER_FUNCTIONS,
|
||||
} from 'constants/antlrQueryConstants';
|
||||
import { OPERATORS as QUERY_BUILDER_OPERATORS } from 'constants/queryBuilder';
|
||||
import {
|
||||
RESTRICTED_GROUP_BY_FIELDS,
|
||||
RESTRICTED_SELECTED_FIELDS,
|
||||
} from 'container/LogsFilters/config';
|
||||
import { MetricsType } from 'container/MetricsApplication/constant';
|
||||
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearchV2/utils';
|
||||
import { chooseAutocompleteFromCustomValue } from 'lib/newQueryBuilder/chooseAutocompleteFromCustomValue';
|
||||
@@ -18,6 +14,10 @@ import {
|
||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import {
|
||||
RESTRICTED_GROUP_BY_FIELDS,
|
||||
RESTRICTED_SELECTED_FIELDS,
|
||||
} from './config';
|
||||
import { LogAttributeBucket } from './constants';
|
||||
import { generateFieldKeyForArray, getDataTypes } from './utils';
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
export const TIME_PICKER_OPTIONS = [
|
||||
{
|
||||
value: 5,
|
||||
label: '5m',
|
||||
},
|
||||
{
|
||||
value: 15,
|
||||
label: '15m',
|
||||
},
|
||||
{
|
||||
value: 30,
|
||||
label: '30m',
|
||||
},
|
||||
{
|
||||
value: 60,
|
||||
label: '1hr',
|
||||
},
|
||||
{
|
||||
value: 360,
|
||||
label: '6hrs',
|
||||
},
|
||||
{
|
||||
value: 720,
|
||||
label: '12hrs',
|
||||
},
|
||||
];
|
||||
@@ -1,270 +0,0 @@
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { green } from '@ant-design/colors';
|
||||
import { Pause, Play, EllipsisVertical } from '@signozhq/icons';
|
||||
import { Button, Flex, Popover, Select, Space } from 'antd';
|
||||
import { LiveTail } from 'api/logs/livetail';
|
||||
import dayjs from 'dayjs';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import getStep from 'lib/getStep';
|
||||
import { throttle } from 'lodash-es';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_AUTO_REFRESH_DISABLED } from 'types/actions/globalTime';
|
||||
import {
|
||||
FLUSH_LOGS,
|
||||
PUSH_LIVE_TAIL_EVENT,
|
||||
SET_LIVE_TAIL_START_TIME,
|
||||
SET_LOADING,
|
||||
TOGGLE_LIVE_TAIL,
|
||||
} from 'types/actions/logs';
|
||||
import { TLogsLiveTailState } from 'types/api/logs/liveTail';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import { TIME_PICKER_OPTIONS } from './config';
|
||||
import { StopContainer, TimePickerCard, TimePickerSelect } from './styles';
|
||||
|
||||
function LogLiveTail({ getLogsAggregate }: Props): JSX.Element {
|
||||
const {
|
||||
liveTail,
|
||||
searchFilter: { queryString },
|
||||
liveTailStartRange,
|
||||
logs,
|
||||
idEnd,
|
||||
idStart,
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const { selectedAutoRefreshInterval } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
const handleLiveTail = (toggleState: TLogsLiveTailState): void => {
|
||||
dispatch({
|
||||
type: TOGGLE_LIVE_TAIL,
|
||||
payload: toggleState,
|
||||
});
|
||||
dispatch({
|
||||
type: UPDATE_AUTO_REFRESH_DISABLED,
|
||||
payload: toggleState === 'PLAYING',
|
||||
});
|
||||
};
|
||||
|
||||
const batchedEventsRef = useRef<ILog[]>([]);
|
||||
|
||||
const pushLiveLog = useCallback(() => {
|
||||
dispatch({
|
||||
type: PUSH_LIVE_TAIL_EVENT,
|
||||
payload: batchedEventsRef.current.reverse(),
|
||||
});
|
||||
batchedEventsRef.current = [];
|
||||
}, [dispatch]);
|
||||
|
||||
const pushLiveLogThrottled = useMemo(
|
||||
() => throttle(pushLiveLog, 1000),
|
||||
[pushLiveLog],
|
||||
);
|
||||
|
||||
const batchLiveLog = useCallback(
|
||||
(e: { data: string }): void => {
|
||||
batchedEventsRef.current.push(JSON.parse(e.data as string) as never);
|
||||
pushLiveLogThrottled();
|
||||
},
|
||||
[pushLiveLogThrottled],
|
||||
);
|
||||
|
||||
const firstLogsId = useMemo(() => logs[0]?.id, [logs]);
|
||||
|
||||
// This ref depicts thats whether the live tail is played from paused state or not.
|
||||
const liveTailSourceRef = useRef<EventSource>();
|
||||
|
||||
useEffect(() => {
|
||||
if (liveTail === 'PLAYING') {
|
||||
const timeStamp = dayjs().subtract(liveTailStartRange, 'minute').valueOf();
|
||||
const queryParams = new URLSearchParams({
|
||||
...(queryString ? { q: queryString } : {}),
|
||||
timestampStart: (timeStamp * 1e6) as never,
|
||||
...(liveTailSourceRef.current && firstLogsId
|
||||
? {
|
||||
idGt: firstLogsId,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
|
||||
if (liveTailSourceRef.current) {
|
||||
liveTailSourceRef.current.close();
|
||||
}
|
||||
|
||||
const source = LiveTail(queryParams.toString());
|
||||
liveTailSourceRef.current = source;
|
||||
source.onmessage = function connectionMessage(e): void {
|
||||
batchLiveLog(e);
|
||||
};
|
||||
source.onerror = function connectionError(event: unknown): void {
|
||||
console.error(event);
|
||||
source.close();
|
||||
dispatch({
|
||||
type: TOGGLE_LIVE_TAIL,
|
||||
payload: 'STOPPED',
|
||||
});
|
||||
dispatch({
|
||||
type: SET_LOADING,
|
||||
payload: false,
|
||||
});
|
||||
notifications.error({
|
||||
message: 'Live tail stopped due to some error.',
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
if (liveTail === 'STOPPED') {
|
||||
liveTailSourceRef.current = undefined;
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [liveTail, queryString, notifications, dispatch]);
|
||||
|
||||
const handleLiveTailStart = (): void => {
|
||||
handleLiveTail('PLAYING');
|
||||
const startTime =
|
||||
dayjs().subtract(liveTailStartRange, 'minute').valueOf() * 1e6;
|
||||
|
||||
const endTime = dayjs().valueOf() * 1e6;
|
||||
|
||||
getLogsAggregate({
|
||||
timestampStart: startTime,
|
||||
timestampEnd: endTime,
|
||||
step: getStep({
|
||||
start: startTime,
|
||||
end: endTime,
|
||||
inputFormat: 'ns',
|
||||
}),
|
||||
q: queryString,
|
||||
...(idStart ? { idGt: idStart } : {}),
|
||||
...(idEnd ? { idLt: idEnd } : {}),
|
||||
});
|
||||
|
||||
if (!liveTailSourceRef.current) {
|
||||
dispatch({
|
||||
type: FLUSH_LOGS,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const OptionsPopOverContent = useMemo(
|
||||
() => (
|
||||
<TimePickerSelect
|
||||
getPopupContainer={popupContainer}
|
||||
disabled={liveTail === 'PLAYING'}
|
||||
value={liveTailStartRange}
|
||||
onChange={(value): void => {
|
||||
if (typeof value === 'number') {
|
||||
dispatch({
|
||||
type: SET_LIVE_TAIL_START_TIME,
|
||||
payload: value,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{TIME_PICKER_OPTIONS.map((optionData) => (
|
||||
<Select.Option key={optionData.label} value={optionData.value}>
|
||||
Last {optionData.label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</TimePickerSelect>
|
||||
),
|
||||
[dispatch, liveTail, liveTailStartRange],
|
||||
);
|
||||
|
||||
const isDisabled = useMemo(
|
||||
() => selectedAutoRefreshInterval?.length > 0,
|
||||
[selectedAutoRefreshInterval],
|
||||
);
|
||||
|
||||
const onLiveTailStop = (): void => {
|
||||
handleLiveTail('STOPPED');
|
||||
dispatch({
|
||||
type: UPDATE_AUTO_REFRESH_DISABLED,
|
||||
payload: false,
|
||||
});
|
||||
dispatch({
|
||||
type: SET_LOADING,
|
||||
payload: false,
|
||||
});
|
||||
if (liveTailSourceRef.current) {
|
||||
liveTailSourceRef.current.close();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TimePickerCard>
|
||||
<Space size={0} align="center">
|
||||
{liveTail === 'PLAYING' ? (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={onLiveTailStop}
|
||||
title="Pause live tail"
|
||||
style={{ background: green[6] }}
|
||||
>
|
||||
<Flex align="center" gap={4}>
|
||||
<span>Pause</span>
|
||||
<Pause size="md" />
|
||||
</Flex>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleLiveTailStart}
|
||||
title="Start live tail"
|
||||
disabled={isDisabled}
|
||||
>
|
||||
<Flex align="center" gap={4}>
|
||||
Go Live <Play size="md" />
|
||||
</Flex>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{liveTail !== 'STOPPED' && (
|
||||
<Button type="dashed" onClick={onLiveTailStop} title="Exit live tail">
|
||||
<StopContainer isDarkMode={isDarkMode} />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Popover
|
||||
getPopupContainer={popupContainer}
|
||||
placement="bottomRight"
|
||||
title="Select Live Tail Timing"
|
||||
trigger="click"
|
||||
content={OptionsPopOverContent}
|
||||
>
|
||||
<EllipsisVertical size="lg" />
|
||||
</Popover>
|
||||
</Space>
|
||||
</TimePickerCard>
|
||||
);
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getLogsAggregate: typeof getLogsAggregate;
|
||||
}
|
||||
|
||||
type Props = DispatchProps;
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(LogLiveTail);
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Card, Select } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const TimePickerCard = styled(Card)`
|
||||
.ant-card-body {
|
||||
display: flex;
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const TimePickerSelect = styled(Select)`
|
||||
min-width: 100px;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
|
||||
export const StopContainer = styled.div<Props>`
|
||||
height: 0.8rem;
|
||||
width: 0.8rem;
|
||||
border-radius: 0.1rem;
|
||||
background-color: ${({ isDarkMode }): string =>
|
||||
isDarkMode ? '#fff' : '#000'};
|
||||
`;
|
||||
@@ -1,95 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { blue } from '@ant-design/colors';
|
||||
import Graph from 'components/Graph';
|
||||
import Spinner from 'components/Spinner';
|
||||
import dayjs from 'dayjs';
|
||||
import useInterval from 'hooks/useInterval';
|
||||
import getStep from 'lib/getStep';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import { Container } from './styles';
|
||||
|
||||
function LogsAggregate({ getLogsAggregate }: DispatchProps): JSX.Element {
|
||||
const {
|
||||
searchFilter: { queryString },
|
||||
idEnd,
|
||||
idStart,
|
||||
isLoadingAggregate,
|
||||
logsAggregate,
|
||||
liveTail,
|
||||
liveTailStartRange,
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
|
||||
useInterval(
|
||||
() => {
|
||||
const startTime =
|
||||
dayjs().subtract(liveTailStartRange, 'minute').valueOf() * 1e6;
|
||||
|
||||
const endTime = dayjs().valueOf() * 1e6;
|
||||
|
||||
getLogsAggregate({
|
||||
timestampStart: startTime,
|
||||
timestampEnd: endTime,
|
||||
step: getStep({
|
||||
start: startTime,
|
||||
end: endTime,
|
||||
inputFormat: 'ns',
|
||||
}),
|
||||
q: queryString,
|
||||
...(idStart ? { idGt: idStart } : {}),
|
||||
...(idEnd ? { idLt: idEnd } : {}),
|
||||
});
|
||||
},
|
||||
60000,
|
||||
liveTail === 'PLAYING',
|
||||
);
|
||||
|
||||
const graphData = useMemo(
|
||||
() => ({
|
||||
labels: logsAggregate.map((s) => new Date(s.timestamp / 1000000)),
|
||||
datasets: [
|
||||
{
|
||||
data: logsAggregate.map((s) => s.value),
|
||||
backgroundColor: blue[4],
|
||||
},
|
||||
],
|
||||
}),
|
||||
[logsAggregate],
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{isLoadingAggregate ? (
|
||||
<Spinner size="default" height="100%" />
|
||||
) : (
|
||||
<Graph
|
||||
name="usage"
|
||||
data={graphData}
|
||||
type="bar"
|
||||
containerHeight="100%"
|
||||
animate
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getLogsAggregate: typeof getLogsAggregate;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(LogsAggregate);
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Card } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled(Card)`
|
||||
position: relative;
|
||||
margin: 0.5rem 0;
|
||||
.ant-card-body {
|
||||
height: 20vh;
|
||||
min-height: 200px;
|
||||
}
|
||||
`;
|
||||
@@ -1,93 +0,0 @@
|
||||
import { ReactNode, useCallback, useMemo, useState } from 'react';
|
||||
import { Loader } from '@signozhq/icons';
|
||||
import { Button, Popover, Spin } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import {
|
||||
IField,
|
||||
IInterestingFields,
|
||||
ISelectedFields,
|
||||
} from 'types/api/logs/fields';
|
||||
|
||||
import { ICON_STYLE } from './config';
|
||||
import { Field } from './styles';
|
||||
|
||||
function FieldItem({
|
||||
name,
|
||||
buttonIcon,
|
||||
buttonOnClick,
|
||||
fieldData,
|
||||
fieldIndex,
|
||||
isLoading,
|
||||
iconHoverText,
|
||||
}: FieldItemProps): JSX.Element {
|
||||
const [isHovered, setIsHovered] = useState<boolean>(false);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const onClickHandler = useCallback(() => {
|
||||
if (!isLoading && buttonOnClick) {
|
||||
buttonOnClick({ fieldData, fieldIndex });
|
||||
}
|
||||
}, [buttonOnClick, fieldData, fieldIndex, isLoading]);
|
||||
|
||||
const renderContent = useMemo(() => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Spin
|
||||
spinning
|
||||
size="small"
|
||||
indicator={<Loader className="animate-spin" />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isHovered) {
|
||||
return (
|
||||
<Popover content={<Typography>{iconHoverText}</Typography>}>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
icon={buttonIcon}
|
||||
onClick={onClickHandler}
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [buttonIcon, iconHoverText, isHovered, isLoading, onClickHandler]);
|
||||
|
||||
const onMouseHoverHandler = useCallback(
|
||||
(value: boolean) => (): void => {
|
||||
setIsHovered(value);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Field
|
||||
onMouseEnter={onMouseHoverHandler(true)}
|
||||
onMouseLeave={onMouseHoverHandler(false)}
|
||||
isDarkMode={isDarkMode}
|
||||
>
|
||||
<Typography style={ICON_STYLE.PLUS}>{name}</Typography>
|
||||
|
||||
{renderContent}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
interface FieldItemProps {
|
||||
name: string;
|
||||
buttonIcon: ReactNode;
|
||||
buttonOnClick: (props: {
|
||||
fieldData: IInterestingFields | ISelectedFields;
|
||||
fieldIndex: number;
|
||||
}) => void;
|
||||
fieldData: IField;
|
||||
fieldIndex: number;
|
||||
isLoading: boolean;
|
||||
iconHoverText: string;
|
||||
}
|
||||
|
||||
export default FieldItem;
|
||||
@@ -1,11 +0,0 @@
|
||||
import { blue, red } from '@ant-design/colors';
|
||||
|
||||
export const RESTRICTED_SELECTED_FIELDS = ['timestamp', 'id'];
|
||||
|
||||
// Fields that can be filtered on but not grouped by in the log details view.
|
||||
export const RESTRICTED_GROUP_BY_FIELDS = ['body', 'trace_id'];
|
||||
|
||||
export const ICON_STYLE = {
|
||||
PLUS: { color: blue[5] },
|
||||
CLOSE: { color: red[5] },
|
||||
};
|
||||
@@ -1,119 +0,0 @@
|
||||
import { ChangeEvent, useCallback, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { CirclePlus, X } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Col } from 'antd';
|
||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import { ICON_STYLE, RESTRICTED_SELECTED_FIELDS } from './config';
|
||||
import FieldItem from './FieldItem';
|
||||
import { CategoryContainer, FieldContainer } from './styles';
|
||||
import { IHandleInterestProps, IHandleRemoveInterestProps } from './types';
|
||||
import { onHandleAddInterest, onHandleRemoveInterest } from './utils';
|
||||
|
||||
function LogsFilters(): JSX.Element {
|
||||
const {
|
||||
fields: { interesting, selected },
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
|
||||
const [selectedFieldLoading, setSelectedFieldLoading] = useState<number[]>([]);
|
||||
const [interestingFieldLoading, setInterestingFieldLoading] = useState<
|
||||
number[]
|
||||
>([]);
|
||||
|
||||
const [filterValuesInput, setFilterValuesInput] = useState('');
|
||||
const handleSearch = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||
setFilterValuesInput((e.target as HTMLInputElement).value);
|
||||
};
|
||||
|
||||
const onHandleAddSelectedToInteresting = useCallback(
|
||||
({ fieldData, fieldIndex }: IHandleInterestProps) =>
|
||||
(): Promise<void> =>
|
||||
onHandleAddInterest({
|
||||
fieldData,
|
||||
fieldIndex,
|
||||
interesting,
|
||||
interestingFieldLoading,
|
||||
setInterestingFieldLoading,
|
||||
selected,
|
||||
}),
|
||||
[interesting, interestingFieldLoading, selected],
|
||||
);
|
||||
|
||||
const onHandleRemoveSelected = useCallback(
|
||||
({ fieldData, fieldIndex }: IHandleRemoveInterestProps) =>
|
||||
(): Promise<void> =>
|
||||
onHandleRemoveInterest({
|
||||
fieldData,
|
||||
fieldIndex,
|
||||
interesting,
|
||||
interestingFieldLoading,
|
||||
selected,
|
||||
setSelectedFieldLoading,
|
||||
}),
|
||||
[interesting, interestingFieldLoading, selected, setSelectedFieldLoading],
|
||||
);
|
||||
|
||||
return (
|
||||
<Col flex="250px">
|
||||
<Input
|
||||
placeholder="Filter Values"
|
||||
onInput={handleSearch}
|
||||
value={filterValuesInput}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
|
||||
<CategoryContainer>
|
||||
<CategoryHeading>SELECTED FIELDS</CategoryHeading>
|
||||
<FieldContainer>
|
||||
{selected
|
||||
.filter((field) => fieldSearchFilter(field.name, filterValuesInput))
|
||||
.filter((field) => RESTRICTED_SELECTED_FIELDS.indexOf(field.name) === -1)
|
||||
.map((field, idx) => (
|
||||
<FieldItem
|
||||
key={`${JSON.stringify(field)}`}
|
||||
name={field.name}
|
||||
fieldData={field}
|
||||
fieldIndex={idx}
|
||||
buttonIcon={<X style={ICON_STYLE.CLOSE} size="md" />}
|
||||
buttonOnClick={onHandleRemoveSelected({
|
||||
fieldData: field,
|
||||
fieldIndex: idx,
|
||||
})}
|
||||
isLoading={selectedFieldLoading.includes(idx)}
|
||||
iconHoverText="Remove from Selected Fields"
|
||||
/>
|
||||
))}
|
||||
</FieldContainer>
|
||||
</CategoryContainer>
|
||||
<CategoryContainer>
|
||||
<CategoryHeading>INTERESTING FIELDS</CategoryHeading>
|
||||
<FieldContainer>
|
||||
{interesting
|
||||
.filter((field) => fieldSearchFilter(field.name, filterValuesInput))
|
||||
.map((field, idx) => (
|
||||
<FieldItem
|
||||
key={`${JSON.stringify(field)}`}
|
||||
name={field.name}
|
||||
fieldData={field}
|
||||
fieldIndex={idx}
|
||||
buttonIcon={<CirclePlus style={ICON_STYLE.PLUS} size="md" />}
|
||||
buttonOnClick={onHandleAddSelectedToInteresting({
|
||||
fieldData: field,
|
||||
fieldIndex: idx,
|
||||
})}
|
||||
isLoading={interestingFieldLoading.includes(idx)}
|
||||
iconHoverText="Add to Selected Fields"
|
||||
/>
|
||||
))}
|
||||
</FieldContainer>
|
||||
</CategoryContainer>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
export default LogsFilters;
|
||||
@@ -1,29 +0,0 @@
|
||||
import { blue, grey } from '@ant-design/colors';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const CategoryContainer = styled.div`
|
||||
margin: 1rem 0;
|
||||
padding-left: 0.2rem;
|
||||
`;
|
||||
|
||||
export const FieldContainer = styled(Typography.Text)`
|
||||
margin: 0.2rem 0;
|
||||
color: ${blue[4]};
|
||||
`;
|
||||
|
||||
export const Field = styled.div<{ isDarkMode: boolean }>`
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.3rem 0.5rem;
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
&:hover {
|
||||
background: ${({ isDarkMode }): string => (isDarkMode ? grey[7] : '#ddd')};
|
||||
}
|
||||
`;
|
||||
|
||||
export const ExtractField = styled(Typography.Text)`
|
||||
color: ${blue[4]};
|
||||
`;
|
||||
@@ -1,36 +0,0 @@
|
||||
import { SetStateAction } from 'react';
|
||||
import {
|
||||
IField,
|
||||
IInterestingFields,
|
||||
ISelectedFields,
|
||||
} from 'types/api/logs/fields';
|
||||
|
||||
type SetLoading = (value: SetStateAction<number[]>) => void;
|
||||
|
||||
export type IHandleInterestProps = {
|
||||
fieldData: IInterestingFields;
|
||||
fieldIndex: number;
|
||||
};
|
||||
|
||||
export type IHandleRemoveInterestProps = {
|
||||
fieldData: ISelectedFields;
|
||||
fieldIndex: number;
|
||||
};
|
||||
|
||||
export interface OnHandleAddInterestProps {
|
||||
setInterestingFieldLoading: SetLoading;
|
||||
fieldIndex: number;
|
||||
fieldData: ISelectedFields;
|
||||
interesting: IField[];
|
||||
interestingFieldLoading: number[];
|
||||
selected: IField[];
|
||||
}
|
||||
|
||||
export interface OnHandleRemoveInterestProps {
|
||||
setSelectedFieldLoading: SetLoading;
|
||||
selected: IField[];
|
||||
interesting: IField[];
|
||||
interestingFieldLoading: number[];
|
||||
fieldData: IInterestingFields;
|
||||
fieldIndex: number;
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import { message } from 'antd';
|
||||
import addToSelectedFields from 'api/logs/AddToSelectedField';
|
||||
import removeSelectedField from 'api/logs/RemoveFromSelectedField';
|
||||
import store from 'store';
|
||||
import {
|
||||
UPDATE_INTERESTING_FIELDS,
|
||||
UPDATE_SELECTED_FIELDS,
|
||||
} from 'types/actions/logs';
|
||||
import { ErrorResponse } from 'types/api';
|
||||
|
||||
import { RESTRICTED_SELECTED_FIELDS } from './config';
|
||||
import { OnHandleAddInterestProps, OnHandleRemoveInterestProps } from './types';
|
||||
|
||||
export const onHandleAddInterest = async ({
|
||||
setInterestingFieldLoading,
|
||||
fieldIndex,
|
||||
fieldData,
|
||||
interesting,
|
||||
interestingFieldLoading,
|
||||
selected,
|
||||
}: OnHandleAddInterestProps): Promise<void> => {
|
||||
const { dispatch } = store;
|
||||
|
||||
setInterestingFieldLoading((prevState: number[]) => {
|
||||
prevState.push(fieldIndex);
|
||||
return [...prevState];
|
||||
});
|
||||
|
||||
try {
|
||||
await addToSelectedFields({
|
||||
...fieldData,
|
||||
selected: true,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_INTERESTING_FIELDS,
|
||||
payload: {
|
||||
field: interesting.filter((e) => e.name !== fieldData.name),
|
||||
type: 'selected',
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_SELECTED_FIELDS,
|
||||
payload: {
|
||||
field: [...selected, fieldData],
|
||||
type: 'selected',
|
||||
},
|
||||
});
|
||||
} catch (errRes) {
|
||||
message.error((errRes as ErrorResponse)?.error);
|
||||
} finally {
|
||||
setInterestingFieldLoading(
|
||||
interestingFieldLoading.filter((e) => e !== fieldIndex),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const onHandleRemoveInterest = async ({
|
||||
setSelectedFieldLoading,
|
||||
selected,
|
||||
interesting,
|
||||
interestingFieldLoading,
|
||||
fieldData,
|
||||
fieldIndex,
|
||||
}: OnHandleRemoveInterestProps): Promise<void> => {
|
||||
if (RESTRICTED_SELECTED_FIELDS.includes(fieldData.name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { dispatch } = store;
|
||||
|
||||
setSelectedFieldLoading((prevState) => {
|
||||
prevState.push(fieldIndex);
|
||||
return [...prevState];
|
||||
});
|
||||
|
||||
try {
|
||||
await removeSelectedField({
|
||||
...fieldData,
|
||||
selected: false,
|
||||
});
|
||||
dispatch({
|
||||
type: UPDATE_SELECTED_FIELDS,
|
||||
payload: {
|
||||
field: selected.filter((e) => e.name !== fieldData.name),
|
||||
type: 'selected',
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_INTERESTING_FIELDS,
|
||||
payload: {
|
||||
field: [...interesting, fieldData],
|
||||
type: 'interesting',
|
||||
},
|
||||
});
|
||||
} catch (errRes) {
|
||||
message.error((errRes as ErrorResponse)?.error);
|
||||
} finally {
|
||||
setSelectedFieldLoading(
|
||||
interestingFieldLoading.filter((e) => e !== fieldIndex),
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Button, Row } from 'antd';
|
||||
|
||||
interface SearchFieldsActionBarProps {
|
||||
applyUpdate: VoidFunction;
|
||||
clearFilters: VoidFunction;
|
||||
}
|
||||
|
||||
export function SearchFieldsActionBar({
|
||||
applyUpdate,
|
||||
clearFilters,
|
||||
}: SearchFieldsActionBarProps): JSX.Element | null {
|
||||
return (
|
||||
<Row style={{ justifyContent: 'flex-end', paddingRight: '2.4rem' }}>
|
||||
<Button
|
||||
type="default"
|
||||
onClick={clearFilters}
|
||||
style={{ marginRight: '1rem' }}
|
||||
>
|
||||
Clear Filter
|
||||
</Button>
|
||||
<Button type="primary" onClick={applyUpdate}>
|
||||
Apply
|
||||
</Button>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
export default SearchFieldsActionBar;
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
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 color="muted" italic>
|
||||
{type}
|
||||
</Typography.Text>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export default FieldKey;
|
||||
@@ -1,259 +0,0 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { SquareX, X } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Select } from 'antd';
|
||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||
import {
|
||||
ConditionalOperators,
|
||||
QueryOperatorsMultiVal,
|
||||
QueryOperatorsSingleVal,
|
||||
} from 'lib/logql/tokens';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import FieldKey from '../FieldKey';
|
||||
import { QueryFieldContainer } from '../styles';
|
||||
import { QueryFields } from '../utils';
|
||||
import { Container, QueryWrapper } from './styles';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
function QueryConditionField({
|
||||
query,
|
||||
queryIndex,
|
||||
onUpdate,
|
||||
}: QueryConditionFieldProps): JSX.Element {
|
||||
const allOptions = Object.values(ConditionalOperators);
|
||||
return (
|
||||
<Select
|
||||
defaultValue={
|
||||
(query as QueryFields).value &&
|
||||
(
|
||||
(query as QueryFields)
|
||||
?.value as unknown as QueryFields as unknown as string
|
||||
).toUpperCase()
|
||||
}
|
||||
onChange={(e): void => {
|
||||
onUpdate({ ...query, value: e }, queryIndex);
|
||||
}}
|
||||
>
|
||||
{allOptions.map((cond) => (
|
||||
<Option key={cond} value={cond} label={cond}>
|
||||
{cond}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
interface QueryFieldProps {
|
||||
query: Query;
|
||||
queryIndex: number;
|
||||
onUpdate: (query: Query, queryIndex: number) => void;
|
||||
onDelete: (queryIndex: number) => void;
|
||||
}
|
||||
function QueryField({
|
||||
query,
|
||||
queryIndex,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
}: QueryFieldProps): JSX.Element | null {
|
||||
const [isDropDownOpen, setIsDropDownOpen] = useState(false);
|
||||
|
||||
const {
|
||||
fields: { selected },
|
||||
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
||||
const getFieldType = useCallback(
|
||||
(inputKey: string): string => {
|
||||
const selectedField = selected.find((field) => inputKey === field.name);
|
||||
if (selectedField) {
|
||||
return selectedField.type;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
[selected],
|
||||
);
|
||||
|
||||
const fieldType = useMemo(
|
||||
() => getFieldType(query[0].value as string),
|
||||
[getFieldType, query],
|
||||
);
|
||||
|
||||
const handleChange = (qIdx: number, value: string): void => {
|
||||
const updatedQuery = [...query];
|
||||
updatedQuery[qIdx].value = value || '';
|
||||
|
||||
if (qIdx === 1) {
|
||||
if (Object.values(QueryOperatorsMultiVal).includes(value)) {
|
||||
if (!Array.isArray(updatedQuery[2].value)) {
|
||||
updatedQuery[2].value = [];
|
||||
}
|
||||
} else if (
|
||||
Object.values(QueryOperatorsSingleVal).includes(value) &&
|
||||
Array.isArray(updatedQuery[2].value)
|
||||
) {
|
||||
updatedQuery[2].value = '';
|
||||
}
|
||||
}
|
||||
onUpdate(updatedQuery, queryIndex);
|
||||
};
|
||||
|
||||
const handleClear = (): void => {
|
||||
onDelete(queryIndex);
|
||||
};
|
||||
if (!Array.isArray(query)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<QueryFieldContainer
|
||||
style={{ ...(queryIndex === 0 && { gridColumnStart: 2 }) }}
|
||||
>
|
||||
<div style={{ flex: 1, minWidth: 100 }}>
|
||||
<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 as string).toUpperCase()
|
||||
: null
|
||||
}
|
||||
onChange={(e): void => handleChange(1, e)}
|
||||
style={{ minWidth: 150 }}
|
||||
>
|
||||
{Object.values({
|
||||
...QueryOperatorsMultiVal,
|
||||
...QueryOperatorsSingleVal,
|
||||
}).map((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 as string)?.toUpperCase(),
|
||||
) ? (
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{ width: '100%' }}
|
||||
open={isDropDownOpen}
|
||||
onChange={(e): void => handleChange(2, e as never)}
|
||||
defaultValue={(query[2] && query[2].value) || []}
|
||||
notFoundContent={null}
|
||||
onInputKeyDown={(): void => setIsDropDownOpen(true)}
|
||||
onSelect={(): void => setIsDropDownOpen(false)}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
onChange={(e): void => {
|
||||
handleChange(2, e.target.value);
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
defaultValue={query[2] && query[2].value}
|
||||
value={query[2] && query[2].value}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
icon={<X size="md" />}
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={handleClear}
|
||||
/>
|
||||
</QueryFieldContainer>
|
||||
);
|
||||
}
|
||||
|
||||
interface QueryConditionFieldProps {
|
||||
query: QueryFields;
|
||||
queryIndex: number;
|
||||
onUpdate: (arg0: unknown, arg1: number) => void;
|
||||
}
|
||||
|
||||
export type Query = { value: string | string[]; type: string }[];
|
||||
|
||||
export interface QueryBuilderProps {
|
||||
keyPrefix: string;
|
||||
onDropDownToggleHandler: (value: boolean) => VoidFunction;
|
||||
fieldsQuery: QueryFields[][];
|
||||
setFieldsQuery: (q: QueryFields[][]) => void;
|
||||
syncKeyPrefix: () => void;
|
||||
}
|
||||
|
||||
function QueryBuilder({
|
||||
keyPrefix,
|
||||
fieldsQuery,
|
||||
setFieldsQuery,
|
||||
onDropDownToggleHandler,
|
||||
syncKeyPrefix,
|
||||
}: QueryBuilderProps): JSX.Element {
|
||||
const handleUpdate = (query: Query, queryIndex: number): void => {
|
||||
const updated = [...fieldsQuery];
|
||||
updated[queryIndex] = query as never; // parseQuery(query) as never;
|
||||
setFieldsQuery(updated);
|
||||
};
|
||||
|
||||
const handleDelete = (queryIndex: number): void => {
|
||||
const updated = [...fieldsQuery];
|
||||
if (queryIndex !== 0) {
|
||||
updated.splice(queryIndex - 1, 2);
|
||||
} else {
|
||||
updated.splice(queryIndex, 2);
|
||||
}
|
||||
|
||||
setFieldsQuery(updated);
|
||||
|
||||
// initiate re-render query panel
|
||||
syncKeyPrefix();
|
||||
};
|
||||
|
||||
const QueryUI = (
|
||||
fieldsQuery: QueryFields[][],
|
||||
): JSX.Element | JSX.Element[] => {
|
||||
const result: JSX.Element[] = [];
|
||||
fieldsQuery.forEach((query, idx) => {
|
||||
if (Array.isArray(query) && query.length > 1) {
|
||||
result.push(
|
||||
<QueryField
|
||||
key={keyPrefix}
|
||||
query={query}
|
||||
queryIndex={idx}
|
||||
onUpdate={handleUpdate}
|
||||
onDelete={handleDelete}
|
||||
/>,
|
||||
);
|
||||
} else {
|
||||
result.push(
|
||||
<div key={keyPrefix}>
|
||||
<QueryConditionField
|
||||
query={Array.isArray(query) ? query[0] : query}
|
||||
queryIndex={idx}
|
||||
onUpdate={handleUpdate as never}
|
||||
/>
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container isMargin={fieldsQuery.length === 0}>
|
||||
<CategoryHeading>LOG QUERY BUILDER</CategoryHeading>
|
||||
<SquareX onClick={onDropDownToggleHandler(false)} size="md" />
|
||||
</Container>
|
||||
|
||||
<QueryWrapper key={keyPrefix}>{QueryUI(fieldsQuery)}</QueryWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default QueryBuilder;
|
||||
@@ -1,17 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface Props {
|
||||
isMargin: boolean;
|
||||
}
|
||||
export const Container = styled.div<Props>`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-bottom: ${(props): string => (props.isMargin ? '2rem' : '0')};
|
||||
`;
|
||||
|
||||
export const QueryWrapper = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr;
|
||||
margin: 0.5rem 0px;
|
||||
`;
|
||||
@@ -1,62 +0,0 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Button } from 'antd';
|
||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||
import map from 'lodash-es/map';
|
||||
import { AppState } from 'store/reducers';
|
||||
// import { ADD_SEARCH_FIELD_QUERY_STRING } from 'types/actions/logs';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import FieldKey from './FieldKey';
|
||||
|
||||
interface SuggestedItemProps {
|
||||
name: string;
|
||||
type: string;
|
||||
applySuggestion: (name: string) => void;
|
||||
}
|
||||
function SuggestedItem({
|
||||
name,
|
||||
type,
|
||||
applySuggestion,
|
||||
}: SuggestedItemProps): JSX.Element {
|
||||
const addSuggestedField = (): void => {
|
||||
applySuggestion(name);
|
||||
};
|
||||
return (
|
||||
<Button
|
||||
type="text"
|
||||
style={{ display: 'block', padding: '0.2rem' }}
|
||||
onClick={addSuggestedField}
|
||||
>
|
||||
<FieldKey name={name} type={type} />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
interface SuggestionsProps {
|
||||
applySuggestion: (name: string) => void;
|
||||
}
|
||||
|
||||
function Suggestions({ applySuggestion }: SuggestionsProps): JSX.Element {
|
||||
const {
|
||||
fields: { selected },
|
||||
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CategoryHeading>SUGGESTIONS</CategoryHeading>
|
||||
<div>
|
||||
{map(selected, (field) => (
|
||||
<SuggestedItem
|
||||
key={JSON.stringify(field)}
|
||||
name={field.name}
|
||||
type={field.type}
|
||||
applySuggestion={applySuggestion}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Suggestions;
|
||||
@@ -1,123 +0,0 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { reverseParser } from 'lib/logql';
|
||||
import { flatten } from 'lodash-es';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import { SearchFieldsActionBar } from './ActionBar';
|
||||
import QueryBuilder from './QueryBuilder/QueryBuilder';
|
||||
import Suggestions from './Suggestions';
|
||||
import {
|
||||
createParsedQueryStructure,
|
||||
fieldsQueryIsvalid,
|
||||
hashCode,
|
||||
initQueryKOVPair,
|
||||
prepareConditionOperator,
|
||||
QueryFields,
|
||||
} from './utils';
|
||||
|
||||
export interface SearchFieldsProps {
|
||||
onDropDownToggleHandler: (value: boolean) => VoidFunction;
|
||||
updateQueryString: (value: string) => void;
|
||||
}
|
||||
|
||||
function SearchFields({
|
||||
onDropDownToggleHandler,
|
||||
updateQueryString,
|
||||
}: SearchFieldsProps): JSX.Element {
|
||||
const {
|
||||
searchFilter: { parsedQuery },
|
||||
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
||||
|
||||
const [fieldsQuery, setFieldsQuery] = useState(
|
||||
createParsedQueryStructure([...parsedQuery] as never[]),
|
||||
);
|
||||
|
||||
const keyPrefixRef = useRef(hashCode(JSON.stringify(fieldsQuery)));
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
const updatedFieldsQuery = createParsedQueryStructure([
|
||||
...parsedQuery,
|
||||
] as never[]);
|
||||
setFieldsQuery(updatedFieldsQuery);
|
||||
const incomingHashCode = hashCode(JSON.stringify(updatedFieldsQuery));
|
||||
if (incomingHashCode !== keyPrefixRef.current) {
|
||||
keyPrefixRef.current = incomingHashCode;
|
||||
}
|
||||
}, [parsedQuery]);
|
||||
|
||||
// syncKeyPrefix initiates re-render. useful in situations like
|
||||
// delete field (in search panel). this method allows condiitonally
|
||||
// setting keyPrefix as doing it on every update of query initiates
|
||||
// a re-render. this is a problem for text fields where input focus goes away.
|
||||
const syncKeyPrefix = (): void => {
|
||||
keyPrefixRef.current = hashCode(JSON.stringify(fieldsQuery));
|
||||
};
|
||||
|
||||
const addSuggestedField = useCallback(
|
||||
(name: string): void => {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const query = [...fieldsQuery];
|
||||
|
||||
if (fieldsQuery.length > 0) {
|
||||
query.push([prepareConditionOperator()]);
|
||||
}
|
||||
|
||||
const newField: QueryFields[] = [];
|
||||
initQueryKOVPair(name).forEach((q) => newField.push(q));
|
||||
|
||||
query.push(newField);
|
||||
keyPrefixRef.current = hashCode(JSON.stringify(query));
|
||||
setFieldsQuery(query);
|
||||
},
|
||||
[fieldsQuery, setFieldsQuery],
|
||||
);
|
||||
|
||||
const applyUpdate = useCallback((): void => {
|
||||
const flatParsedQuery = flatten(fieldsQuery);
|
||||
|
||||
if (!fieldsQueryIsvalid(flatParsedQuery)) {
|
||||
notifications.error({
|
||||
message: 'Please enter a valid criteria for each of the selected fields',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
keyPrefixRef.current = hashCode(JSON.stringify(flatParsedQuery));
|
||||
updateQueryString(reverseParser(flatParsedQuery));
|
||||
onDropDownToggleHandler(false)();
|
||||
}, [fieldsQuery, notifications, onDropDownToggleHandler, updateQueryString]);
|
||||
|
||||
const clearFilters = useCallback((): void => {
|
||||
keyPrefixRef.current = hashCode(JSON.stringify([]));
|
||||
setFieldsQuery([]);
|
||||
updateQueryString('');
|
||||
}, [updateQueryString]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<QueryBuilder
|
||||
key={keyPrefixRef.current}
|
||||
keyPrefix={keyPrefixRef.current}
|
||||
onDropDownToggleHandler={onDropDownToggleHandler}
|
||||
fieldsQuery={fieldsQuery}
|
||||
setFieldsQuery={setFieldsQuery}
|
||||
syncKeyPrefix={syncKeyPrefix}
|
||||
/>
|
||||
<SearchFieldsActionBar
|
||||
applyUpdate={applyUpdate}
|
||||
clearFilters={clearFilters}
|
||||
/>
|
||||
<Suggestions applySuggestion={addSuggestedField} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
export default SearchFields;
|
||||
@@ -1,16 +0,0 @@
|
||||
import { blue } from '@ant-design/colors';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const QueryFieldContainer = styled.div`
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin: 0.1rem 0.5rem 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-radius: 0.25rem;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
&:hover {
|
||||
background: ${blue[6]};
|
||||
}
|
||||
`;
|
||||
@@ -1,137 +0,0 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import {
|
||||
ConditionalOperators,
|
||||
QueryTypes,
|
||||
ValidTypeSequence,
|
||||
ValidTypeValue,
|
||||
} from 'lib/logql/tokens';
|
||||
|
||||
export interface QueryFields {
|
||||
type: keyof typeof QueryTypes;
|
||||
value: string | string[];
|
||||
}
|
||||
|
||||
export function fieldsQueryIsvalid(queryFields: QueryFields[]): boolean {
|
||||
let lastOp: string;
|
||||
let result = true;
|
||||
queryFields.forEach((q, idx) => {
|
||||
if (!q.value || q.value === null || q.value === '') {
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (Array.isArray(q.value) && q.value.length === 0) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
const nextOp = idx < queryFields.length ? queryFields[idx + 1] : undefined;
|
||||
if (!ValidTypeSequence(lastOp?.type, q?.type, nextOp?.type)) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (!ValidTypeValue(lastOp?.value, q.value)) {
|
||||
result = false;
|
||||
}
|
||||
lastOp = q;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export const queryKOVPair = (): QueryFields[] => [
|
||||
{
|
||||
type: QueryTypes.QUERY_KEY,
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
type: QueryTypes.QUERY_OPERATOR,
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
type: QueryTypes.QUERY_VALUE,
|
||||
value: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const initQueryKOVPair = (
|
||||
name: string = null,
|
||||
op: string = null,
|
||||
value: string | string[] = null,
|
||||
): QueryFields[] => [
|
||||
{
|
||||
type: QueryTypes.QUERY_KEY,
|
||||
value: name,
|
||||
},
|
||||
{
|
||||
type: QueryTypes.QUERY_OPERATOR,
|
||||
value: op,
|
||||
},
|
||||
{
|
||||
type: QueryTypes.QUERY_VALUE,
|
||||
value: value,
|
||||
},
|
||||
];
|
||||
|
||||
export const prepareConditionOperator = (
|
||||
op: string = ConditionalOperators.AND,
|
||||
): QueryFields => {
|
||||
return {
|
||||
type: QueryTypes.CONDITIONAL_OPERATOR,
|
||||
value: op,
|
||||
};
|
||||
};
|
||||
|
||||
export const createParsedQueryStructure = (
|
||||
parsedQuery = [],
|
||||
): QueryFields[][] => {
|
||||
if (parsedQuery.length === 0) {
|
||||
return parsedQuery;
|
||||
}
|
||||
|
||||
const structuredArray = [queryKOVPair()];
|
||||
|
||||
let cond;
|
||||
let qCtr = -1;
|
||||
parsedQuery.forEach((query) => {
|
||||
if (cond) {
|
||||
structuredArray.push(cond);
|
||||
structuredArray.push(queryKOVPair());
|
||||
cond = null;
|
||||
qCtr = -1;
|
||||
}
|
||||
const stagingArr = structuredArray.at(-1);
|
||||
const prevQuery =
|
||||
Array.isArray(stagingArr) && qCtr >= 0 ? stagingArr[qCtr] : null;
|
||||
|
||||
if (query.type === QueryTypes.QUERY_KEY) {
|
||||
stagingArr[qCtr + 1] = query;
|
||||
} else if (
|
||||
query.type === QueryTypes.QUERY_OPERATOR &&
|
||||
prevQuery &&
|
||||
prevQuery.type === QueryTypes.QUERY_KEY
|
||||
) {
|
||||
stagingArr[qCtr + 1] = query;
|
||||
} else if (
|
||||
query.type === QueryTypes.QUERY_VALUE &&
|
||||
prevQuery &&
|
||||
prevQuery.type === QueryTypes.QUERY_OPERATOR
|
||||
) {
|
||||
stagingArr[qCtr + 1] = query;
|
||||
} else if (query.type === QueryTypes.CONDITIONAL_OPERATOR) {
|
||||
cond = query;
|
||||
}
|
||||
qCtr++;
|
||||
});
|
||||
return structuredArray;
|
||||
};
|
||||
|
||||
export const hashCode = (s: string): string => {
|
||||
if (!s) {
|
||||
return '0';
|
||||
}
|
||||
return `${Math.abs(
|
||||
[...s].reduce((a, b) => {
|
||||
a = (a << 5) - a + b.codePointAt(0);
|
||||
return a & a;
|
||||
}, 0),
|
||||
)}`;
|
||||
};
|
||||
@@ -1,230 +0,0 @@
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { Input, InputRef, Popover } from 'antd';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import getStep from 'lib/getStep';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
import { getIdConditions } from 'pages/Logs/utils';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { GetLogsFields } from 'store/actions/logs/getFields';
|
||||
import { getLogs } from 'store/actions/logs/getLogs';
|
||||
import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
FLUSH_LOGS,
|
||||
SET_LOADING,
|
||||
SET_LOADING_AGGREGATE,
|
||||
TOGGLE_LIVE_TAIL,
|
||||
} from 'types/actions/logs';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import SearchFields from './SearchFields';
|
||||
import { Container, DropDownContainer } from './styles';
|
||||
import { useSearchParser } from './useSearchParser';
|
||||
|
||||
function SearchFilter({
|
||||
getLogs,
|
||||
getLogsAggregate,
|
||||
getLogsFields,
|
||||
}: SearchFilterProps): JSX.Element {
|
||||
const { updateQueryString, queryString } = useSearchParser();
|
||||
const [searchText, setSearchText] = useState(queryString);
|
||||
const [showDropDown, setShowDropDown] = useState(false);
|
||||
const searchRef = useRef<InputRef>(null);
|
||||
const { logLinesPerPage, idEnd, idStart, liveTail, order } = useSelector<
|
||||
AppState,
|
||||
ILogsReducer
|
||||
>((state) => state.logs);
|
||||
|
||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
// keep sync with url queryString
|
||||
useEffect(() => {
|
||||
setSearchText(queryString);
|
||||
}, [queryString]);
|
||||
|
||||
const debouncedupdateQueryString = useMemo(
|
||||
() => debounce(updateQueryString, 300),
|
||||
[updateQueryString],
|
||||
);
|
||||
|
||||
const onDropDownToggleHandler = useCallback(
|
||||
(value: boolean) => (): void => {
|
||||
setShowDropDown(value);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleSearch = useCallback(
|
||||
(customQuery: string) => {
|
||||
getLogsFields();
|
||||
const { maxTime, minTime } = globalTime;
|
||||
|
||||
if (liveTail === 'PLAYING') {
|
||||
dispatch({
|
||||
type: TOGGLE_LIVE_TAIL,
|
||||
payload: 'PAUSED',
|
||||
});
|
||||
dispatch({
|
||||
type: FLUSH_LOGS,
|
||||
});
|
||||
dispatch({
|
||||
type: TOGGLE_LIVE_TAIL,
|
||||
payload: liveTail,
|
||||
});
|
||||
dispatch({
|
||||
type: SET_LOADING,
|
||||
payload: false,
|
||||
});
|
||||
|
||||
getLogsAggregate({
|
||||
timestampStart: minTime,
|
||||
timestampEnd: maxTime,
|
||||
step: getStep({
|
||||
start: minTime,
|
||||
end: maxTime,
|
||||
inputFormat: 'ns',
|
||||
}),
|
||||
q: customQuery,
|
||||
...(idStart ? { idGt: idStart } : {}),
|
||||
...(idEnd ? { idLt: idEnd } : {}),
|
||||
});
|
||||
} else {
|
||||
getLogs({
|
||||
q: customQuery,
|
||||
limit: logLinesPerPage,
|
||||
orderBy: 'timestamp',
|
||||
order,
|
||||
timestampStart: minTime,
|
||||
timestampEnd: maxTime,
|
||||
...getIdConditions(idStart, idEnd, order),
|
||||
});
|
||||
|
||||
getLogsAggregate({
|
||||
timestampStart: minTime,
|
||||
timestampEnd: maxTime,
|
||||
step: getStep({
|
||||
start: minTime,
|
||||
end: maxTime,
|
||||
inputFormat: 'ns',
|
||||
}),
|
||||
q: customQuery,
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
dispatch,
|
||||
getLogs,
|
||||
getLogsAggregate,
|
||||
idEnd,
|
||||
idStart,
|
||||
liveTail,
|
||||
logLinesPerPage,
|
||||
globalTime,
|
||||
getLogsFields,
|
||||
order,
|
||||
],
|
||||
);
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
const urlQueryString = urlQuery.get('q');
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: SET_LOADING,
|
||||
payload: true,
|
||||
});
|
||||
dispatch({
|
||||
type: SET_LOADING_AGGREGATE,
|
||||
payload: true,
|
||||
});
|
||||
|
||||
const debouncedHandleSearch = debounce(handleSearch, 600);
|
||||
|
||||
debouncedHandleSearch(urlQueryString || '');
|
||||
|
||||
return (): void => {
|
||||
debouncedHandleSearch.cancel();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
urlQueryString,
|
||||
idEnd,
|
||||
idStart,
|
||||
logLinesPerPage,
|
||||
dispatch,
|
||||
globalTime.maxTime,
|
||||
globalTime.minTime,
|
||||
order,
|
||||
]);
|
||||
|
||||
const onPopOverChange = useCallback(
|
||||
(isVisible: boolean) => {
|
||||
onDropDownToggleHandler(isVisible)();
|
||||
},
|
||||
[onDropDownToggleHandler],
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Popover
|
||||
getPopupContainer={popupContainer}
|
||||
placement="bottom"
|
||||
content={
|
||||
<DropDownContainer>
|
||||
<SearchFields
|
||||
updateQueryString={updateQueryString}
|
||||
onDropDownToggleHandler={onDropDownToggleHandler}
|
||||
/>
|
||||
</DropDownContainer>
|
||||
}
|
||||
trigger="click"
|
||||
overlayInnerStyle={{
|
||||
width: `${searchRef?.current?.input?.offsetWidth || 0}px`,
|
||||
}}
|
||||
open={showDropDown}
|
||||
destroyTooltipOnHide
|
||||
onOpenChange={onPopOverChange}
|
||||
>
|
||||
<Input.Search
|
||||
ref={searchRef}
|
||||
placeholder="Search Filter"
|
||||
value={searchText}
|
||||
onChange={(e): void => {
|
||||
const { value } = e.target;
|
||||
setSearchText(value);
|
||||
}}
|
||||
onSearch={debouncedupdateQueryString}
|
||||
allowClear
|
||||
/>
|
||||
</Popover>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getLogs: typeof getLogs;
|
||||
getLogsAggregate: typeof getLogsAggregate;
|
||||
getLogsFields: typeof GetLogsFields;
|
||||
}
|
||||
|
||||
type SearchFilterProps = DispatchProps;
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
getLogs: bindActionCreators(getLogs, dispatch),
|
||||
getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch),
|
||||
getLogsFields: bindActionCreators(GetLogsFields, dispatch),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(memo(SearchFilter));
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Card } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const DropDownContainer = styled(Card)`
|
||||
.ant-card-body {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
`;
|
||||
@@ -1,81 +0,0 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { getMinMaxForSelectedTime } from 'lib/getMinMax';
|
||||
import history from 'lib/history';
|
||||
import { parseQuery } from 'lib/logql';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
SET_SEARCH_QUERY_PARSED_PAYLOAD,
|
||||
SET_SEARCH_QUERY_STRING,
|
||||
} from 'types/actions/logs';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import { getGlobalTime } from './utils';
|
||||
|
||||
export function useSearchParser(): {
|
||||
queryString: string;
|
||||
parsedQuery: unknown;
|
||||
updateQueryString: (arg0: string) => void;
|
||||
} {
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
const {
|
||||
searchFilter: { parsedQuery, queryString },
|
||||
order,
|
||||
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
const parsedFilters = urlQuery.get('q');
|
||||
|
||||
const { minTime, maxTime, selectedTime } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((store) => store.globalTime);
|
||||
|
||||
const updateQueryString = useCallback(
|
||||
(updatedQueryString: string) => {
|
||||
history.replace({
|
||||
pathname: history.location.pathname,
|
||||
search: `?${QueryParams.q}=${updatedQueryString}&${QueryParams.order}=${order}`,
|
||||
});
|
||||
|
||||
const globalTime = getMinMaxForSelectedTime(selectedTime, minTime, maxTime);
|
||||
|
||||
dispatch({
|
||||
type: SET_SEARCH_QUERY_STRING,
|
||||
payload: {
|
||||
searchQueryString: updatedQueryString,
|
||||
globalTime: getGlobalTime(selectedTime, globalTime),
|
||||
},
|
||||
});
|
||||
|
||||
const parsedQueryFromString = parseQuery(updatedQueryString);
|
||||
if (!isEqual(parsedQuery, parsedQueryFromString)) {
|
||||
dispatch({
|
||||
type: SET_SEARCH_QUERY_PARSED_PAYLOAD,
|
||||
payload: parsedQueryFromString,
|
||||
});
|
||||
}
|
||||
},
|
||||
// need to hide this warning as we don't want to update the query string on every change
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[dispatch, parsedQuery, selectedTime, queryString],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
updateQueryString(parsedFilters || '');
|
||||
}, [parsedFilters, updateQueryString]);
|
||||
|
||||
return {
|
||||
queryString,
|
||||
parsedQuery,
|
||||
updateQueryString,
|
||||
};
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
} from 'container/TopNav/DateTimeSelectionV2/types';
|
||||
import { GetMinMaxPayload } from 'lib/getMinMax';
|
||||
|
||||
export const getGlobalTime = (
|
||||
selectedTime: Time | CustomTimeType,
|
||||
globalTime: GetMinMaxPayload,
|
||||
): GetMinMaxPayload | undefined => {
|
||||
if (selectedTime === 'custom') {
|
||||
return undefined;
|
||||
}
|
||||
return globalTime;
|
||||
};
|
||||
@@ -1,164 +0,0 @@
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
import { Card } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
// components
|
||||
import ListLogView from 'components/Logs/ListLogView';
|
||||
import RawLogView from 'components/Logs/RawLogView';
|
||||
import LogsTableView from 'components/Logs/TableView';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { CARD_BODY_STYLE } from 'constants/card';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||
// interfaces
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
// styles
|
||||
import { Container, Heading } from './styles';
|
||||
|
||||
import './logsTable.styles.scss';
|
||||
|
||||
export type LogViewMode = 'raw' | 'table' | 'list';
|
||||
|
||||
type LogsTableProps = {
|
||||
viewMode: LogViewMode;
|
||||
linesPerRow: number;
|
||||
};
|
||||
|
||||
function LogsTable(props: LogsTableProps): JSX.Element {
|
||||
const { viewMode, linesPerRow } = props;
|
||||
|
||||
const { activeLog, onClearActiveLog, onAddToQuery, onSetActiveLog } =
|
||||
useActiveLog();
|
||||
|
||||
const {
|
||||
logs,
|
||||
fields: { selected },
|
||||
isLoading,
|
||||
liveTail,
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
|
||||
const isLiveTail = useMemo(
|
||||
() => logs.length === 0 && liveTail === 'PLAYING',
|
||||
[logs?.length, liveTail],
|
||||
);
|
||||
|
||||
const isNoLogs = useMemo(
|
||||
() => logs.length === 0 && liveTail === 'STOPPED',
|
||||
[logs?.length, liveTail],
|
||||
);
|
||||
|
||||
const { options } = useOptionsMenu({
|
||||
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
|
||||
// this component will alwyays be called on old logs explorer page itself!
|
||||
dataSource: DataSource.LOGS,
|
||||
// and we do not have table / timeseries aggregated views in the old logs explorer!
|
||||
aggregateOperator: StringOperators.NOOP,
|
||||
});
|
||||
|
||||
const getItemContent = useCallback(
|
||||
(index: number): JSX.Element => {
|
||||
const log = logs[index];
|
||||
|
||||
if (viewMode === 'raw') {
|
||||
return (
|
||||
<RawLogView
|
||||
key={log.id}
|
||||
data={log}
|
||||
linesPerRow={linesPerRow}
|
||||
selectedFields={selected}
|
||||
fontSize={options.fontSize}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ListLogView
|
||||
key={log.id}
|
||||
logData={log}
|
||||
selectedFields={selected}
|
||||
linesPerRow={linesPerRow}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onSetActiveLog={onSetActiveLog}
|
||||
fontSize={options.fontSize}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[
|
||||
logs,
|
||||
viewMode,
|
||||
selected,
|
||||
linesPerRow,
|
||||
onAddToQuery,
|
||||
onSetActiveLog,
|
||||
options.fontSize,
|
||||
],
|
||||
);
|
||||
|
||||
const renderContent = useMemo(() => {
|
||||
if (viewMode === 'table') {
|
||||
return (
|
||||
<LogsTableView
|
||||
onClickExpand={onSetActiveLog}
|
||||
logs={logs}
|
||||
fields={selected}
|
||||
linesPerRow={linesPerRow}
|
||||
fontSize={options.fontSize}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="logs-card" bodyStyle={CARD_BODY_STYLE}>
|
||||
<OverlayScrollbar isVirtuoso>
|
||||
<Virtuoso totalCount={logs.length} itemContent={getItemContent} />
|
||||
</OverlayScrollbar>
|
||||
</Card>
|
||||
);
|
||||
}, [
|
||||
getItemContent,
|
||||
linesPerRow,
|
||||
logs,
|
||||
onSetActiveLog,
|
||||
options.fontSize,
|
||||
selected,
|
||||
viewMode,
|
||||
]);
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner height={20} tip="Getting Logs" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{viewMode !== 'table' && (
|
||||
<Heading>
|
||||
<Typography.Text>Event</Typography.Text>
|
||||
</Heading>
|
||||
)}
|
||||
|
||||
{isLiveTail && <Typography>Getting live logs...</Typography>}
|
||||
|
||||
{isNoLogs && <Typography>No logs lines found</Typography>}
|
||||
|
||||
{renderContent}
|
||||
<LogDetail
|
||||
selectedTab={VIEW_TYPES.OVERVIEW}
|
||||
log={activeLog}
|
||||
onClose={onClearActiveLog}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onClickActionItem={onAddToQuery}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(LogsTable);
|
||||
@@ -1,3 +0,0 @@
|
||||
.logs-card {
|
||||
flex: 1;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Card } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const Heading = styled(Card)`
|
||||
margin-bottom: 0.1rem;
|
||||
height: 32px;
|
||||
.ant-card-body {
|
||||
padding: 0.3rem 0.5rem;
|
||||
}
|
||||
`;
|
||||
@@ -1,8 +1,6 @@
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
export const buttonText: Record<string, string> = {
|
||||
[ROUTES.LOGS_EXPLORER]: 'Old Explorer',
|
||||
[ROUTES.TRACE]: 'New Explorer',
|
||||
[ROUTES.OLD_LOGS_EXPLORER]: 'New Explorer',
|
||||
[ROUTES.TRACES_EXPLORER]: 'Old Explorer',
|
||||
};
|
||||
|
||||
@@ -15,11 +15,9 @@ function NewExplorerCTA(): JSX.Element | null {
|
||||
const location = useLocation();
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
|
||||
const isTraceOrLogsExplorerPage = useMemo(
|
||||
const isTracesExplorerPage = useMemo(
|
||||
() =>
|
||||
location.pathname === ROUTES.LOGS_EXPLORER ||
|
||||
location.pathname === ROUTES.TRACE ||
|
||||
location.pathname === ROUTES.OLD_LOGS_EXPLORER ||
|
||||
location.pathname === ROUTES.TRACES_EXPLORER,
|
||||
[location.pathname],
|
||||
);
|
||||
@@ -27,12 +25,8 @@ function NewExplorerCTA(): JSX.Element | null {
|
||||
const onClickHandler = useCallback(
|
||||
(e?: React.MouseEvent): void => {
|
||||
let targetPath: string;
|
||||
if (location.pathname === ROUTES.LOGS_EXPLORER) {
|
||||
targetPath = ROUTES.OLD_LOGS_EXPLORER;
|
||||
} else if (location.pathname === ROUTES.TRACE) {
|
||||
if (location.pathname === ROUTES.TRACE) {
|
||||
targetPath = ROUTES.TRACES_EXPLORER;
|
||||
} else if (location.pathname === ROUTES.OLD_LOGS_EXPLORER) {
|
||||
targetPath = ROUTES.LOGS_EXPLORER;
|
||||
} else if (location.pathname === ROUTES.TRACES_EXPLORER) {
|
||||
targetPath = ROUTES.TRACE;
|
||||
} else {
|
||||
@@ -58,7 +52,7 @@ function NewExplorerCTA(): JSX.Element | null {
|
||||
[location.pathname, onClickHandler],
|
||||
);
|
||||
|
||||
if (!isTraceOrLogsExplorerPage) {
|
||||
if (!isTracesExplorerPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -66,10 +60,6 @@ function NewExplorerCTA(): JSX.Element | null {
|
||||
return button;
|
||||
}
|
||||
|
||||
if (location.pathname === ROUTES.LOGS_EXPLORER) {
|
||||
return button;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="new-explorer-cta-with-badge">
|
||||
{button}
|
||||
|
||||
@@ -3,10 +3,9 @@ import './FormatField.styles.scss';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
|
||||
import { FieldTitle } from '../styles';
|
||||
import { OptionsMenuConfig } from '../types';
|
||||
import { LogViewMode, OptionsMenuConfig } from '../types';
|
||||
import { FormatFieldWrapper } from './styles';
|
||||
|
||||
function FormatField({ config }: FormatFieldProps): JSX.Element | null {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { InputNumberProps, RadioProps, SelectProps } from 'antd';
|
||||
import { TelemetryFieldKey } from 'api/v5/v5';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
|
||||
export type LogViewMode = 'raw' | 'table' | 'list';
|
||||
|
||||
export enum FontSize {
|
||||
SMALL = 'small',
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useQueries } from 'react-query';
|
||||
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
|
||||
import { TelemetryFieldKey } from 'api/v5/v5';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
import { useGetQueryKeySuggestions } from 'hooks/querySuggestions/useGetQueryKeySuggestions';
|
||||
import useDebounce from 'hooks/useDebounce';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
@@ -33,6 +32,7 @@ import {
|
||||
import {
|
||||
FontSize,
|
||||
InitialOptions,
|
||||
LogViewMode,
|
||||
OptionsMenuConfig,
|
||||
OptionsQuery,
|
||||
} from './types';
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { OPERATORS, QueryBuilderKeys } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { MetricsType } from 'container/MetricsApplication/constant';
|
||||
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearchV2/utils';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { getGeneratedFilterQueryString } from 'lib/getGeneratedFilterQueryString';
|
||||
import { chooseAutocompleteFromCustomValue } from 'lib/newQueryBuilder/chooseAutocompleteFromCustomValue';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import {
|
||||
BaseAutocompleteData,
|
||||
DataTypes,
|
||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { UseActiveLog } from './types';
|
||||
@@ -40,22 +32,10 @@ export function getOldLogsOperatorFromNew(operator: string): string {
|
||||
}
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export const useActiveLog = (): UseActiveLog => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const {
|
||||
searchFilter: { queryString },
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
const queryClient = useQueryClient();
|
||||
const { pathname } = useLocation();
|
||||
const history = useHistory();
|
||||
const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const isLogsPage = useMemo(
|
||||
() => pathname === ROUTES.OLD_LOGS_EXPLORER,
|
||||
[pathname],
|
||||
);
|
||||
|
||||
const [activeLog, setActiveLog] = useState<ILog | null>(null);
|
||||
|
||||
// Close drawer/clear active log when query in URL changes
|
||||
@@ -72,26 +52,9 @@ export const useActiveLog = (): UseActiveLog => {
|
||||
prevQueryRef.current = compositeQuery;
|
||||
}, [compositeQuery]);
|
||||
|
||||
const onSetDetailedLogData = useCallback(
|
||||
(logData: ILog) => {
|
||||
dispatch({
|
||||
type: SET_DETAILED_LOG_DATA,
|
||||
payload: logData,
|
||||
});
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const onSetActiveLog = useCallback(
|
||||
(nextActiveLog: ILog): void => {
|
||||
if (isLogsPage) {
|
||||
onSetDetailedLogData(nextActiveLog);
|
||||
} else {
|
||||
setActiveLog(nextActiveLog);
|
||||
}
|
||||
},
|
||||
[isLogsPage, onSetDetailedLogData],
|
||||
);
|
||||
const onSetActiveLog = useCallback((nextActiveLog: ILog): void => {
|
||||
setActiveLog(nextActiveLog);
|
||||
}, []);
|
||||
|
||||
const onClearActiveLog = useCallback((): void => setActiveLog(null), []);
|
||||
|
||||
@@ -204,26 +167,12 @@ export const useActiveLog = (): UseActiveLog => {
|
||||
},
|
||||
[currentQuery, notifications, queryClient, redirectWithQueryBuilderData],
|
||||
);
|
||||
const onAddToQueryLogs = useCallback(
|
||||
(fieldKey: string, fieldValue: string, operator: string) => {
|
||||
const newOperator = getOldLogsOperatorFromNew(operator);
|
||||
const updatedQueryString = getGeneratedFilterQueryString(
|
||||
fieldKey,
|
||||
fieldValue,
|
||||
newOperator,
|
||||
queryString,
|
||||
);
|
||||
|
||||
history.replace(`${ROUTES.OLD_LOGS_EXPLORER}?q=${updatedQueryString}`);
|
||||
},
|
||||
[history, queryString],
|
||||
);
|
||||
|
||||
return {
|
||||
activeLog,
|
||||
onSetActiveLog,
|
||||
onClearActiveLog,
|
||||
onAddToQuery: isLogsPage ? onAddToQueryLogs : onAddToQueryExplorer,
|
||||
onAddToQuery: onAddToQueryExplorer,
|
||||
onGroupByAttribute,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { logqlQueries } from 'lib/__fixtures__/logql';
|
||||
import parser from 'lib/logql/parser';
|
||||
|
||||
describe('lib/logql/parser', () => {
|
||||
it('parse valid queries', () => {
|
||||
logqlQueries.forEach((queryObject) => {
|
||||
expect(parser(queryObject.query)).toStrictEqual(queryObject.parsedQuery);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import { logqlQueries } from 'lib/__fixtures__/logql';
|
||||
import { reverseParser } from 'lib/logql/reverseParser';
|
||||
|
||||
describe('lib/logql/reverseParser', () => {
|
||||
it('reverse parse valid queries', () => {
|
||||
logqlQueries.forEach((queryObject) => {
|
||||
expect(reverseParser(queryObject.parsedQuery)).toStrictEqual(
|
||||
queryObject.query,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
import { logqlQueries } from 'lib/__fixtures__/logql';
|
||||
import { splitter } from 'lib/logql/splitter';
|
||||
|
||||
describe('lib/logql/splitter', () => {
|
||||
it('splitter valid quereies', () => {
|
||||
logqlQueries.forEach((queryObject) => {
|
||||
expect(splitter(queryObject.query)).toStrictEqual(queryObject.splitterQuery);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
import { generateFilterQuery } from './logs/generateFilterQuery';
|
||||
|
||||
export const getGeneratedFilterQueryString = (
|
||||
fieldKey: string,
|
||||
fieldValue: string,
|
||||
operator: string,
|
||||
queryString: string,
|
||||
): string => {
|
||||
let updatedQueryString = queryString || '';
|
||||
|
||||
const generatedString = generateFilterQuery({
|
||||
fieldKey,
|
||||
fieldValue,
|
||||
type: operator,
|
||||
});
|
||||
|
||||
if (updatedQueryString.length === 0) {
|
||||
updatedQueryString += `${generatedString}`;
|
||||
} else {
|
||||
updatedQueryString += ` AND ${generatedString}`;
|
||||
}
|
||||
|
||||
return updatedQueryString;
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
export class ErrorConvertToFullText extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'ErrorConvertToFullText';
|
||||
Object.setPrototypeOf(this, ErrorConvertToFullText.prototype);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
export class ErrorInvalidQueryPair extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'ErrorInvalidQueryPair';
|
||||
Object.setPrototypeOf(this, ErrorInvalidQueryPair.prototype);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './ConvertToFullText';
|
||||
export * from './InvalidQueryPair';
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from './parser';
|
||||
export * from './reverseParser';
|
||||
export * from './splitter';
|
||||
export * from './tokens';
|
||||
@@ -1,149 +0,0 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import {
|
||||
ErrorConvertToFullText,
|
||||
ErrorInvalidQueryPair,
|
||||
} from 'lib/logql/errors';
|
||||
import splitter from 'lib/logql/splitter';
|
||||
import {
|
||||
ConditionalOperators,
|
||||
QueryOperatorsMultiVal,
|
||||
QueryOperatorsSingleVal,
|
||||
QueryTypes,
|
||||
} from 'lib/logql/tokens';
|
||||
|
||||
const validateMultiValue = (queryToken: string): boolean => {
|
||||
const queryValues = [];
|
||||
let start;
|
||||
let isQuoteStart = false;
|
||||
if (queryToken[0] === '(' && queryToken[queryToken.length - 1] === ')') {
|
||||
for (let idx = 1; idx < queryToken.length - 1; idx += 1) {
|
||||
if (queryToken[idx] === "'") {
|
||||
if (queryToken[idx - 1] === '\\') {
|
||||
// skip
|
||||
} else if (isQuoteStart) {
|
||||
isQuoteStart = false;
|
||||
queryValues.push(queryToken.slice(start, idx));
|
||||
} else {
|
||||
isQuoteStart = true;
|
||||
start = idx + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return queryValues;
|
||||
};
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export const parseQuery = (queryString): any => {
|
||||
let parsedRaw = [];
|
||||
const generateQuery = (queryToken): void => {
|
||||
const prevToken = parsedRaw[parsedRaw.length - 1];
|
||||
|
||||
// Is a QUERY_KEY
|
||||
if (
|
||||
prevToken === undefined ||
|
||||
prevToken.type === QueryTypes.CONDITIONAL_OPERATOR
|
||||
) {
|
||||
parsedRaw.push({
|
||||
type: QueryTypes.QUERY_KEY,
|
||||
value: queryToken,
|
||||
});
|
||||
}
|
||||
// Is a QUERY_OPERATOR
|
||||
else if (prevToken && prevToken.type === QueryTypes.QUERY_KEY) {
|
||||
if (
|
||||
Object.values({
|
||||
...QueryOperatorsMultiVal,
|
||||
...QueryOperatorsSingleVal,
|
||||
}).find((op) => op.toLowerCase() === queryToken.toLowerCase())
|
||||
) {
|
||||
parsedRaw.push({
|
||||
type: QueryTypes.QUERY_OPERATOR,
|
||||
value: queryToken,
|
||||
});
|
||||
} else {
|
||||
throw new ErrorInvalidQueryPair(
|
||||
'Expected conditional operator received',
|
||||
queryToken,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Is a QUERY_VALUE
|
||||
else if (prevToken && prevToken.type === QueryTypes.QUERY_OPERATOR) {
|
||||
// Check for multi value
|
||||
let value = queryToken;
|
||||
// if (
|
||||
// typeof queryToken === 'string' &&
|
||||
// queryToken.length >= 2 &&
|
||||
// queryToken[0] === "'" &&
|
||||
// queryToken[queryToken.length - 1] === "'"
|
||||
// ) {
|
||||
// value = queryToken.slice(1, queryToken.length - 1);
|
||||
// }
|
||||
if (
|
||||
Object.values(QueryOperatorsMultiVal).some(
|
||||
(operator) => operator.toLowerCase() === prevToken.value.toLowerCase(),
|
||||
)
|
||||
) {
|
||||
value = validateMultiValue(queryToken);
|
||||
if (value === false) {
|
||||
throw new ErrorConvertToFullText();
|
||||
}
|
||||
}
|
||||
|
||||
parsedRaw.push({
|
||||
type: QueryTypes.QUERY_VALUE,
|
||||
value,
|
||||
});
|
||||
} else if (prevToken && prevToken.type === QueryTypes.QUERY_VALUE) {
|
||||
if (
|
||||
Object.values(ConditionalOperators).find(
|
||||
(op) => op.toLowerCase() === queryToken.toLowerCase(),
|
||||
)
|
||||
) {
|
||||
parsedRaw.push({
|
||||
type: QueryTypes.CONDITIONAL_OPERATOR,
|
||||
value: queryToken,
|
||||
});
|
||||
} else {
|
||||
throw new ErrorInvalidQueryPair(
|
||||
'Expected conditional operator received',
|
||||
queryToken,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Not a Key
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const spaceSplittedQUery = splitter(queryString);
|
||||
spaceSplittedQUery.forEach((q) => {
|
||||
generateQuery(q);
|
||||
});
|
||||
} catch (e: Error) {
|
||||
if (e instanceof ErrorInvalidQueryPair) {
|
||||
//
|
||||
} else if (e instanceof ErrorConvertToFullText) {
|
||||
parsedRaw = [
|
||||
{
|
||||
type: QueryTypes.QUERY_KEY,
|
||||
value: 'FULLTEXT',
|
||||
},
|
||||
{
|
||||
type: QueryTypes.QUERY_OPERATOR,
|
||||
value: 'CONTAINS',
|
||||
},
|
||||
{
|
||||
type: QueryTypes.QUERY_VALUE,
|
||||
value: String.raw`${queryString}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
return parsedRaw;
|
||||
};
|
||||
|
||||
export default parseQuery;
|
||||
@@ -1,39 +0,0 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { QueryTypes, StringTypeQueryOperators } from './tokens';
|
||||
|
||||
export const reverseParser = (
|
||||
parserQueryArr: { type: string; value: any }[] = [],
|
||||
): string => {
|
||||
let queryString = '';
|
||||
let lastToken: { type: string; value: any };
|
||||
parserQueryArr.forEach((query) => {
|
||||
if (queryString) {
|
||||
queryString += ' ';
|
||||
}
|
||||
|
||||
if (Array.isArray(query.value) && query.value.length > 0) {
|
||||
// if the values are array type, here we spread them in
|
||||
// ('a', 'b') format
|
||||
const queryValuesMapped = query.value.map((val) => `'${val}'`).join(',');
|
||||
queryString += `(${queryValuesMapped})`;
|
||||
} else {
|
||||
if (
|
||||
query.type === QueryTypes.QUERY_VALUE &&
|
||||
lastToken.type === QueryTypes.QUERY_OPERATOR &&
|
||||
Object.values(StringTypeQueryOperators).includes(lastToken.value)
|
||||
) {
|
||||
// for operators that need string type value, here we append single
|
||||
// quotes. if the content has single quote they would be removed
|
||||
queryString += `'${query.value?.replace(/'/g, '')}'`;
|
||||
} else {
|
||||
queryString += query.value;
|
||||
}
|
||||
}
|
||||
lastToken = query;
|
||||
});
|
||||
|
||||
return queryString;
|
||||
};
|
||||
|
||||
export default reverseParser;
|
||||
@@ -1,51 +0,0 @@
|
||||
// @ts-nocheck
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export const splitter = (queryString: string): string[] => {
|
||||
const splittedParts: string[] = [];
|
||||
let start = 0;
|
||||
let isBracketStart = false;
|
||||
let isQuoteStart = false;
|
||||
|
||||
const pushPart = (idx): void => {
|
||||
splittedParts.push(queryString.slice(start, idx));
|
||||
start = idx + 1;
|
||||
};
|
||||
for (let idx = 0; idx < queryString.length; idx += 1) {
|
||||
const currentChar = queryString[idx];
|
||||
|
||||
if (currentChar === ' ') {
|
||||
if (!isBracketStart && !isQuoteStart) {
|
||||
pushPart(idx);
|
||||
}
|
||||
} else if (currentChar === '(') {
|
||||
isBracketStart = true;
|
||||
} else if (currentChar === ')') {
|
||||
if (queryString[idx - 1] !== '\\') {
|
||||
pushPart(idx + 1);
|
||||
isBracketStart = false;
|
||||
}
|
||||
if (isQuoteStart) {
|
||||
isQuoteStart = false;
|
||||
}
|
||||
} else if (currentChar === "'") {
|
||||
if (isQuoteStart) {
|
||||
if (queryString[idx - 1] !== '\\' && !isBracketStart) {
|
||||
pushPart(idx + 1);
|
||||
isQuoteStart = false;
|
||||
}
|
||||
} else {
|
||||
isQuoteStart = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process remaining part
|
||||
if (start < queryString.length) {
|
||||
pushPart(queryString.length);
|
||||
}
|
||||
|
||||
return splittedParts.map((s) => String.raw`${s}`).filter(Boolean);
|
||||
};
|
||||
|
||||
export default splitter;
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface ILogQLParsedQueryItem {
|
||||
type: string;
|
||||
value: string | string[];
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { InputNumber, Row, Space } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
interface PopoverContentProps {
|
||||
linesPerRow: number;
|
||||
handleLinesPerRowChange: (l: unknown) => void;
|
||||
}
|
||||
|
||||
function PopoverContent({
|
||||
linesPerRow,
|
||||
handleLinesPerRowChange,
|
||||
}: PopoverContentProps): JSX.Element {
|
||||
return (
|
||||
<Row align="middle">
|
||||
<Space align="center">
|
||||
<Typography>Max lines per Row </Typography>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={10}
|
||||
value={linesPerRow}
|
||||
onChange={handleLinesPerRowChange}
|
||||
/>
|
||||
</Space>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export default PopoverContent;
|
||||
@@ -1,48 +0,0 @@
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
import { ViewModeOption } from './types';
|
||||
|
||||
export const viewModeOptionList: ViewModeOption[] = [
|
||||
{
|
||||
key: 'raw',
|
||||
label: 'Raw',
|
||||
value: 'raw',
|
||||
},
|
||||
{
|
||||
key: 'table',
|
||||
label: 'Table',
|
||||
value: 'table',
|
||||
},
|
||||
{
|
||||
key: 'list',
|
||||
label: 'List',
|
||||
value: 'list',
|
||||
},
|
||||
];
|
||||
|
||||
export const logsOptions = ['raw', 'table'];
|
||||
|
||||
export const defaultSelectStyle: CSSProperties = {
|
||||
minWidth: '6rem',
|
||||
};
|
||||
|
||||
export enum OrderPreferenceItems {
|
||||
DESC = 'desc',
|
||||
ASC = 'asc',
|
||||
}
|
||||
|
||||
export const orderItems: OrderPreference[] = [
|
||||
{
|
||||
name: 'Descending',
|
||||
enum: OrderPreferenceItems.DESC,
|
||||
},
|
||||
{
|
||||
name: 'Ascending',
|
||||
enum: OrderPreferenceItems.ASC,
|
||||
},
|
||||
];
|
||||
|
||||
export interface OrderPreference {
|
||||
name: string;
|
||||
enum: OrderPreferenceItems;
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
// utils
|
||||
import { useCallback, useLayoutEffect, useMemo } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import get from 'api/browser/localstorage/get';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
// interfaces
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
import { setLinesPerRow } from 'store/actions/logs/setLInesPerRow';
|
||||
// actions
|
||||
import { setViewMode } from 'store/actions/logs/setViewMode';
|
||||
import { AppState } from 'store/reducers';
|
||||
|
||||
import { viewModeOptionList } from './config';
|
||||
import { SelectedLogViewData } from './types';
|
||||
import { isLogViewMode } from './utils';
|
||||
|
||||
export const useSelectedLogView = (): SelectedLogViewData => {
|
||||
const dispatch = useDispatch();
|
||||
const viewMode = useSelector<AppState, LogViewMode>(
|
||||
(state) => state.logs.viewMode,
|
||||
);
|
||||
const linesPerRow = useSelector<AppState, number>(
|
||||
(state) => state.logs.linesPerRow,
|
||||
);
|
||||
|
||||
const viewModeOption = useMemo(
|
||||
() =>
|
||||
viewModeOptionList.find(
|
||||
(viewModeOption) => viewModeOption.value === viewMode,
|
||||
) ?? viewModeOptionList[0],
|
||||
[viewMode],
|
||||
);
|
||||
|
||||
const handleViewModeChange = useCallback(
|
||||
(selectedViewMode: LogViewMode) => {
|
||||
dispatch(setViewMode(selectedViewMode));
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const handleViewModeOptionChange = useCallback(
|
||||
({ key }: { key: string }) => {
|
||||
if (isLogViewMode(key)) {
|
||||
handleViewModeChange(key);
|
||||
}
|
||||
},
|
||||
[handleViewModeChange],
|
||||
);
|
||||
|
||||
const handleLinesPerRowChange = useCallback(
|
||||
(selectedLinesPerRow: unknown) => {
|
||||
if (typeof selectedLinesPerRow === 'number') {
|
||||
dispatch(setLinesPerRow(selectedLinesPerRow));
|
||||
}
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const storedViewMode = get(LOCALSTORAGE.LOGS_VIEW_MODE);
|
||||
if (storedViewMode) {
|
||||
handleViewModeChange(storedViewMode as LogViewMode);
|
||||
}
|
||||
|
||||
const storedLinesPerRow = get(LOCALSTORAGE.LOGS_LINES_PER_ROW);
|
||||
if (storedLinesPerRow) {
|
||||
handleLinesPerRowChange(+storedLinesPerRow);
|
||||
}
|
||||
}, [handleViewModeChange, handleLinesPerRowChange]);
|
||||
|
||||
return {
|
||||
viewModeOptionList,
|
||||
viewModeOption,
|
||||
viewMode,
|
||||
handleViewModeChange,
|
||||
handleViewModeOptionChange,
|
||||
linesPerRow,
|
||||
handleLinesPerRowChange,
|
||||
};
|
||||
};
|
||||
@@ -1,156 +0,0 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Button, Col, Popover, Row, Select, Space } from 'antd';
|
||||
import { Divider } from '@signozhq/ui/divider';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import LogControls from 'container/LogControls';
|
||||
import LogDetailedView from 'container/LogDetailedView';
|
||||
import LogLiveTail from 'container/LogLiveTail';
|
||||
import LogsAggregate from 'container/LogsAggregate';
|
||||
import LogsFilters from 'container/LogsFilters';
|
||||
import LogsSearchFilter from 'container/LogsSearchFilter';
|
||||
import LogsTable from 'container/LogsTable';
|
||||
import history from 'lib/history';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { SET_LOGS_ORDER } from 'types/actions/logs';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import {
|
||||
defaultSelectStyle,
|
||||
logsOptions,
|
||||
orderItems,
|
||||
OrderPreferenceItems,
|
||||
} from './config';
|
||||
import { useSelectedLogView } from './hooks';
|
||||
import PopoverContent from './PopoverContent';
|
||||
import SpaceContainer from './styles';
|
||||
|
||||
import './logs.styles.scss';
|
||||
|
||||
function OldLogsExplorer(): JSX.Element {
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
const { order } = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
||||
const location = useLocation();
|
||||
|
||||
const {
|
||||
viewModeOptionList,
|
||||
viewModeOption,
|
||||
viewMode,
|
||||
handleViewModeOptionChange,
|
||||
linesPerRow,
|
||||
handleLinesPerRowChange,
|
||||
} = useSelectedLogView();
|
||||
|
||||
const renderPopoverContent = useCallback(
|
||||
() => (
|
||||
<PopoverContent
|
||||
linesPerRow={linesPerRow}
|
||||
handleLinesPerRowChange={handleLinesPerRowChange}
|
||||
/>
|
||||
),
|
||||
[linesPerRow, handleLinesPerRowChange],
|
||||
);
|
||||
|
||||
const isFormatButtonVisible = useMemo(
|
||||
() => logsOptions.includes(viewMode),
|
||||
[viewMode],
|
||||
);
|
||||
|
||||
const selectedViewModeOption = useMemo(
|
||||
() => viewModeOption.value.toString(),
|
||||
[viewModeOption.value],
|
||||
);
|
||||
|
||||
const onChangeVeiwMode = useCallback(
|
||||
(key: string) => {
|
||||
handleViewModeOptionChange({
|
||||
key,
|
||||
});
|
||||
},
|
||||
[handleViewModeOptionChange],
|
||||
);
|
||||
|
||||
const handleChangeOrder = (value: OrderPreferenceItems): void => {
|
||||
dispatch({
|
||||
type: SET_LOGS_ORDER,
|
||||
payload: value,
|
||||
});
|
||||
const params = new URLSearchParams(location.search);
|
||||
params.set(QueryParams.order, value);
|
||||
history.push({ search: params.toString() });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="old-logs-explorer">
|
||||
<SpaceContainer
|
||||
split={<Divider type="vertical" />}
|
||||
align="center"
|
||||
direction="horizontal"
|
||||
>
|
||||
<LogsSearchFilter />
|
||||
<LogLiveTail />
|
||||
</SpaceContainer>
|
||||
|
||||
<LogsAggregate />
|
||||
|
||||
<Row gutter={20} wrap={false}>
|
||||
<LogsFilters />
|
||||
<Col flex={1} className="logs-col-container">
|
||||
<Row>
|
||||
<Col flex={1}>
|
||||
<Space align="baseline" direction="horizontal">
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
style={defaultSelectStyle}
|
||||
value={selectedViewModeOption}
|
||||
onChange={onChangeVeiwMode}
|
||||
>
|
||||
{viewModeOptionList.map((option) => (
|
||||
<Select.Option key={option.value}>{option.label}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
{isFormatButtonVisible && (
|
||||
<Popover
|
||||
getPopupContainer={popupContainer}
|
||||
placement="right"
|
||||
content={renderPopoverContent}
|
||||
>
|
||||
<Button>Format</Button>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
style={defaultSelectStyle}
|
||||
defaultValue={order}
|
||||
onChange={handleChangeOrder}
|
||||
>
|
||||
{orderItems.map((item) => (
|
||||
<Select.Option key={item.enum}>{item.name}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Space>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<LogControls />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<LogsTable viewMode={viewMode} linesPerRow={linesPerRow} />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<LogDetailedView />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default OldLogsExplorer;
|
||||
@@ -1,4 +0,0 @@
|
||||
.logs-col-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { Space } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const SpaceContainer = styled(Space)`
|
||||
.ant-space-item:nth-child(1) {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export default SpaceContainer;
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { ItemType } from 'antd/es/menu/hooks/useItems';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
|
||||
export type ViewModeOption = ItemType & {
|
||||
label: string;
|
||||
value: LogViewMode;
|
||||
};
|
||||
|
||||
export type SelectedLogViewData = {
|
||||
viewModeOptionList: ViewModeOption[];
|
||||
viewModeOption: ViewModeOption;
|
||||
viewMode: LogViewMode;
|
||||
handleViewModeChange: (s: LogViewMode) => void;
|
||||
handleViewModeOptionChange: ({ key }: { key: string }) => void;
|
||||
linesPerRow: number;
|
||||
handleLinesPerRowChange: (l: unknown) => void;
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
|
||||
import { OrderPreferenceItems, viewModeOptionList } from './config';
|
||||
|
||||
export const isLogViewMode = (value: unknown): value is LogViewMode =>
|
||||
typeof value === 'string' &&
|
||||
viewModeOptionList.some((option) => option.key === value);
|
||||
|
||||
export const getIdConditions = (
|
||||
idStart: string,
|
||||
idEnd: string,
|
||||
order: OrderPreferenceItems,
|
||||
): Record<string, string> => {
|
||||
const idConditions: Record<string, string> = {};
|
||||
|
||||
if (idStart && order === OrderPreferenceItems.ASC) {
|
||||
idConditions.idLt = idStart;
|
||||
} else if (idStart) {
|
||||
idConditions.idGt = idStart;
|
||||
}
|
||||
|
||||
if (idEnd && order === OrderPreferenceItems.ASC) {
|
||||
idConditions.idGt = idEnd;
|
||||
} else if (idEnd) {
|
||||
idConditions.idLt = idEnd;
|
||||
}
|
||||
|
||||
return idConditions;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TelemetryFieldKey } from 'api/v5/v5';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
import { LogViewMode } from 'container/OptionsMenu/types';
|
||||
import {
|
||||
defaultLogsSelectedColumns,
|
||||
defaultTraceSelectedColumns,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
import { LogViewMode } from 'container/OptionsMenu/types';
|
||||
import { defaultLogsSelectedColumns } from 'container/OptionsMenu/constants';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import { FormattingOptions } from 'providers/preferences/types';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TelemetryFieldKey } from 'api/v5/v5';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
import { LogViewMode } from 'container/OptionsMenu/types';
|
||||
import {
|
||||
defaultLogsSelectedColumns,
|
||||
defaultOptionsQuery,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { TelemetryFieldKey } from 'api/v5/v5';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
import { LogViewMode } from 'container/OptionsMenu/types';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import {
|
||||
FormattingOptions,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TelemetryFieldKey } from 'api/v5/v5';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
import { LogViewMode } from 'container/OptionsMenu/types';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
|
||||
export enum PreferenceMode {
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import GetSearchFields from 'api/logs/GetSearchFields';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { SET_FIELDS } from 'types/actions/logs';
|
||||
|
||||
const IGNORED_SELECTED_FIELDS = ['timestamp'];
|
||||
|
||||
export const GetLogsFields =
|
||||
(): ((dispatch: Dispatch<AppActions>) => void) =>
|
||||
async (dispatch): Promise<void> => {
|
||||
const response = await GetSearchFields();
|
||||
if (response.payload) {
|
||||
dispatch({
|
||||
type: SET_FIELDS,
|
||||
payload: {
|
||||
interesting: response.payload.interesting,
|
||||
selected: response.payload.selected.filter(
|
||||
(field) => !IGNORED_SELECTED_FIELDS.includes(field.name),
|
||||
),
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
import GetLogs from 'api/logs/GetLogs';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
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: Props): ((dispatch: Dispatch<AppActions>) => void) =>
|
||||
async (dispatch): Promise<void> => {
|
||||
dispatch({
|
||||
type: SET_LOADING,
|
||||
payload: true,
|
||||
});
|
||||
|
||||
const response = await GetLogs(props);
|
||||
|
||||
if (response.payload) {
|
||||
dispatch({
|
||||
type: SET_LOGS,
|
||||
payload: response.payload,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: SET_LOGS,
|
||||
payload: [],
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: SET_LOADING,
|
||||
payload: false,
|
||||
});
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
import GetLogsAggregate from 'api/logs/GetLogsAggregate';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
SET_LOADING_AGGREGATE,
|
||||
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): ((dispatch: Dispatch<AppActions>) => void) =>
|
||||
async (dispatch): Promise<void> => {
|
||||
dispatch({
|
||||
type: SET_LOADING_AGGREGATE,
|
||||
payload: true,
|
||||
});
|
||||
|
||||
const response = await GetLogsAggregate(props);
|
||||
if (response.payload) {
|
||||
const convertedArray: ILogsAggregate[] = Object.values(response.payload).map(
|
||||
(data) => ({ ...data, time: new Date(data.timestamp / 1e6) }),
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: SET_LOGS_AGGREGATE_SERIES,
|
||||
payload: convertedArray,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: SET_LOGS_AGGREGATE_SERIES,
|
||||
payload: [],
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: SET_LOADING_AGGREGATE,
|
||||
payload: false,
|
||||
});
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import set from 'api/browser/localstorage/set';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { SET_LINES_PER_ROW } from 'types/actions/logs';
|
||||
|
||||
type ActionSetLinesPerRow = { type: typeof SET_LINES_PER_ROW; payload: number };
|
||||
|
||||
export function setLinesPerRow(lines: number): ActionSetLinesPerRow {
|
||||
set(LOCALSTORAGE.LOGS_LINES_PER_ROW, lines.toString());
|
||||
|
||||
return {
|
||||
type: SET_LINES_PER_ROW,
|
||||
payload: lines,
|
||||
};
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import set from 'api/browser/localstorage/set';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
import { SET_VIEW_MODE } from 'types/actions/logs';
|
||||
|
||||
type ActionSetViewMode = { type: typeof SET_VIEW_MODE; payload: LogViewMode };
|
||||
|
||||
export function setViewMode(viewMode: LogViewMode): ActionSetViewMode {
|
||||
set(LOCALSTORAGE.LOGS_VIEW_MODE, viewMode);
|
||||
|
||||
return {
|
||||
type: SET_VIEW_MODE,
|
||||
payload: viewMode,
|
||||
};
|
||||
}
|
||||
@@ -6,10 +6,6 @@ import {
|
||||
UPDATE_AUTO_REFRESH_INTERVAL,
|
||||
UPDATE_TIME_INTERVAL,
|
||||
} from 'types/actions/globalTime';
|
||||
import {
|
||||
RESET_ID_START_AND_END,
|
||||
SET_SEARCH_QUERY_STRING,
|
||||
} from 'types/actions/logs';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
const initialState: GlobalReducer = {
|
||||
@@ -57,29 +53,6 @@ const globalTimeReducer = (
|
||||
};
|
||||
}
|
||||
|
||||
case RESET_ID_START_AND_END: {
|
||||
return {
|
||||
...state,
|
||||
maxTime: action.payload.maxTime,
|
||||
minTime: action.payload.minTime,
|
||||
};
|
||||
}
|
||||
|
||||
case SET_SEARCH_QUERY_STRING: {
|
||||
const { globalTime } = action.payload;
|
||||
if (globalTime) {
|
||||
return {
|
||||
...state,
|
||||
maxTime: globalTime.maxTime,
|
||||
minTime: globalTime.minTime,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { combineReducers } from 'redux';
|
||||
|
||||
import appReducer from './app';
|
||||
import globalTimeReducer from './global';
|
||||
import { LogsReducer } from './logs';
|
||||
import metricsReducers from './metric';
|
||||
import { ServiceMapReducer } from './serviceMap';
|
||||
import traceReducer from './trace';
|
||||
@@ -16,7 +15,6 @@ const reducers = combineReducers({
|
||||
serviceMap: ServiceMapReducer,
|
||||
app: appReducer,
|
||||
metrics: metricsReducers,
|
||||
logs: LogsReducer,
|
||||
});
|
||||
|
||||
export type AppState = ReturnType<typeof reducers>;
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
import ROUTES from 'constants/routes';
|
||||
import { parseQuery } from 'lib/logql';
|
||||
import { OrderPreferenceItems } from 'pages/Logs/config';
|
||||
import {
|
||||
ADD_SEARCH_FIELD_QUERY_STRING,
|
||||
FLUSH_LOGS,
|
||||
GET_FIELDS,
|
||||
GET_NEXT_LOG_LINES,
|
||||
GET_PREVIOUS_LOG_LINES,
|
||||
LogsActions,
|
||||
PUSH_LIVE_TAIL_EVENT,
|
||||
RESET_ID_START_AND_END,
|
||||
SET_DETAILED_LOG_DATA,
|
||||
SET_FIELDS,
|
||||
SET_LINES_PER_ROW,
|
||||
SET_LIVE_TAIL_START_TIME,
|
||||
SET_LOADING,
|
||||
SET_LOADING_AGGREGATE,
|
||||
SET_LOG_LINES_PER_PAGE,
|
||||
SET_LOGS,
|
||||
SET_LOGS_AGGREGATE_SERIES,
|
||||
SET_LOGS_ORDER,
|
||||
SET_SEARCH_QUERY_PARSED_PAYLOAD,
|
||||
SET_SEARCH_QUERY_STRING,
|
||||
SET_VIEW_MODE,
|
||||
STOP_LIVE_TAIL,
|
||||
TOGGLE_LIVE_TAIL,
|
||||
UPDATE_INTERESTING_FIELDS,
|
||||
UPDATE_SELECTED_FIELDS,
|
||||
} from 'types/actions/logs';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
const supportedLogsOrder = [
|
||||
OrderPreferenceItems.ASC,
|
||||
OrderPreferenceItems.DESC,
|
||||
];
|
||||
|
||||
function getLogsOrder(): OrderPreferenceItems {
|
||||
// set the value of order from the URL only when order query param is present and the user is landing on the old logs explorer page
|
||||
if (window.location.pathname === withBasePath(ROUTES.OLD_LOGS_EXPLORER)) {
|
||||
const orderParam = new URLSearchParams(window.location.search).get('order');
|
||||
|
||||
if (orderParam) {
|
||||
// check if the order passed is supported else pass the default order
|
||||
if (supportedLogsOrder.includes(orderParam as OrderPreferenceItems)) {
|
||||
return orderParam as OrderPreferenceItems;
|
||||
}
|
||||
|
||||
return OrderPreferenceItems.DESC;
|
||||
}
|
||||
return OrderPreferenceItems.DESC;
|
||||
}
|
||||
|
||||
return OrderPreferenceItems.DESC;
|
||||
}
|
||||
|
||||
const initialState: ILogsReducer = {
|
||||
fields: {
|
||||
interesting: [],
|
||||
selected: [],
|
||||
},
|
||||
searchFilter: {
|
||||
queryString: '',
|
||||
parsedQuery: [],
|
||||
},
|
||||
logs: [],
|
||||
logLinesPerPage: 200,
|
||||
linesPerRow: 2,
|
||||
viewMode: 'raw',
|
||||
idEnd: '',
|
||||
idStart: '',
|
||||
isLoading: false,
|
||||
isLoadingAggregate: false,
|
||||
logsAggregate: [],
|
||||
liveTail: 'STOPPED',
|
||||
liveTailStartRange: 15,
|
||||
selectedLogId: null,
|
||||
detailedLog: null,
|
||||
order: getLogsOrder(),
|
||||
};
|
||||
|
||||
export const LogsReducer = (
|
||||
state = initialState,
|
||||
action: LogsActions,
|
||||
): ILogsReducer => {
|
||||
switch (action.type) {
|
||||
case SET_LOADING: {
|
||||
return {
|
||||
...state,
|
||||
isLoading: action.payload,
|
||||
};
|
||||
}
|
||||
|
||||
case SET_LOADING_AGGREGATE: {
|
||||
return {
|
||||
...state,
|
||||
isLoadingAggregate: action.payload,
|
||||
};
|
||||
}
|
||||
|
||||
case GET_FIELDS:
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
|
||||
case SET_FIELDS: {
|
||||
const newFields = action.payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fields: newFields,
|
||||
};
|
||||
}
|
||||
|
||||
case SET_SEARCH_QUERY_STRING: {
|
||||
return {
|
||||
...state,
|
||||
searchFilter: {
|
||||
...state.searchFilter,
|
||||
queryString: action.payload.searchQueryString,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case SET_SEARCH_QUERY_PARSED_PAYLOAD: {
|
||||
return {
|
||||
...state,
|
||||
searchFilter: {
|
||||
...state.searchFilter,
|
||||
parsedQuery: action.payload,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case ADD_SEARCH_FIELD_QUERY_STRING: {
|
||||
const updatedQueryString = `${state?.searchFilter?.queryString || ''}${
|
||||
state.searchFilter.queryString && state.searchFilter.queryString.length > 0
|
||||
? ' and '
|
||||
: ''
|
||||
}${action.payload}`;
|
||||
|
||||
const updatedParsedQuery = parseQuery(updatedQueryString);
|
||||
return {
|
||||
...state,
|
||||
searchFilter: {
|
||||
...state.searchFilter,
|
||||
queryString: updatedQueryString,
|
||||
parsedQuery: updatedParsedQuery,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case SET_LOGS: {
|
||||
const logsData = action.payload;
|
||||
return {
|
||||
...state,
|
||||
logs: logsData,
|
||||
};
|
||||
}
|
||||
|
||||
case SET_LOGS_ORDER: {
|
||||
const order = action.payload;
|
||||
return {
|
||||
...state,
|
||||
order,
|
||||
idStart: '',
|
||||
idEnd: '',
|
||||
};
|
||||
}
|
||||
|
||||
case SET_LOG_LINES_PER_PAGE: {
|
||||
return {
|
||||
...state,
|
||||
logLinesPerPage: action.payload.logsLinesPerPage,
|
||||
};
|
||||
}
|
||||
|
||||
case GET_PREVIOUS_LOG_LINES: {
|
||||
const idStart = state.logs.length > 0 ? state.logs[0].id : '';
|
||||
return {
|
||||
...state,
|
||||
idStart,
|
||||
idEnd: '',
|
||||
};
|
||||
}
|
||||
|
||||
case GET_NEXT_LOG_LINES: {
|
||||
const idEnd =
|
||||
state.logs.length > 0 ? state.logs[state.logs.length - 1].id : '';
|
||||
return {
|
||||
...state,
|
||||
idStart: '',
|
||||
idEnd,
|
||||
};
|
||||
}
|
||||
|
||||
case RESET_ID_START_AND_END: {
|
||||
return {
|
||||
...state,
|
||||
idEnd: '',
|
||||
idStart: '',
|
||||
};
|
||||
}
|
||||
|
||||
case SET_LOGS_AGGREGATE_SERIES: {
|
||||
return {
|
||||
...state,
|
||||
logsAggregate: action.payload,
|
||||
};
|
||||
}
|
||||
|
||||
case SET_DETAILED_LOG_DATA: {
|
||||
return {
|
||||
...state,
|
||||
detailedLog: action.payload,
|
||||
};
|
||||
}
|
||||
|
||||
case TOGGLE_LIVE_TAIL: {
|
||||
return {
|
||||
...state,
|
||||
liveTail: action.payload,
|
||||
};
|
||||
}
|
||||
case STOP_LIVE_TAIL: {
|
||||
return {
|
||||
...state,
|
||||
logs: [],
|
||||
liveTail: 'STOPPED',
|
||||
};
|
||||
}
|
||||
case PUSH_LIVE_TAIL_EVENT: {
|
||||
return {
|
||||
...state,
|
||||
logs: action.payload.concat(state.logs).slice(0, 100),
|
||||
};
|
||||
}
|
||||
case SET_LIVE_TAIL_START_TIME: {
|
||||
return {
|
||||
...state,
|
||||
liveTailStartRange: action.payload,
|
||||
};
|
||||
}
|
||||
case FLUSH_LOGS: {
|
||||
return {
|
||||
...state,
|
||||
logs: [],
|
||||
};
|
||||
}
|
||||
|
||||
case SET_LINES_PER_ROW: {
|
||||
return {
|
||||
...state,
|
||||
linesPerRow: action.payload,
|
||||
};
|
||||
}
|
||||
|
||||
case SET_VIEW_MODE: {
|
||||
return {
|
||||
...state,
|
||||
viewMode: action.payload,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_INTERESTING_FIELDS: {
|
||||
return {
|
||||
...state,
|
||||
fields: {
|
||||
...state.fields,
|
||||
interesting: action.payload.field,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_SELECTED_FIELDS: {
|
||||
return {
|
||||
...state,
|
||||
fields: {
|
||||
...state.fields,
|
||||
selected: action.payload.field,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default LogsReducer;
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
Time,
|
||||
} from 'container/TopNav/DateTimeSelectionV2/types';
|
||||
|
||||
import { ResetIdStartAndEnd, SetSearchQueryString } from './logs';
|
||||
|
||||
export const UPDATE_TIME_INTERVAL = 'UPDATE_TIME_INTERVAL';
|
||||
export const GLOBAL_TIME_LOADING_START = 'GLOBAL_TIME_LOADING_START';
|
||||
export const UPDATE_AUTO_REFRESH_DISABLED = 'UPDATE_AUTO_REFRESH_DISABLED';
|
||||
@@ -42,6 +40,4 @@ export type GlobalTimeAction =
|
||||
| UpdateTimeInterval
|
||||
| GlobalTimeLoading
|
||||
| UpdateAutoRefreshDisabled
|
||||
| UpdateAutoRefreshInterval
|
||||
| ResetIdStartAndEnd
|
||||
| SetSearchQueryString;
|
||||
| UpdateAutoRefreshInterval;
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import { AppAction } from './app';
|
||||
import { GlobalTimeAction } from './globalTime';
|
||||
import { LogsActions } from './logs';
|
||||
import { MetricsActions } from './metrics';
|
||||
import { TraceActions } from './trace';
|
||||
|
||||
type AppActions =
|
||||
| AppAction
|
||||
| GlobalTimeAction
|
||||
| MetricsActions
|
||||
| TraceActions
|
||||
| LogsActions;
|
||||
type AppActions = AppAction | GlobalTimeAction | MetricsActions | TraceActions;
|
||||
|
||||
export default AppActions;
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
import { Pagination } from 'hooks/queryPagination';
|
||||
import { ILogQLParsedQueryItem } from 'lib/logql/types';
|
||||
import { OrderPreferenceItems } from 'pages/Logs/config';
|
||||
import { IField, IFieldMoveToSelected, IFields } from 'types/api/logs/fields';
|
||||
import { TLogsLiveTailState } from 'types/api/logs/liveTail';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { ILogsAggregate } from 'types/api/logs/logAggregate';
|
||||
|
||||
import { GlobalTime } from './globalTime';
|
||||
|
||||
export const GET_FIELDS = 'LOGS_GET_FIELDS';
|
||||
export const SET_FIELDS = 'LOGS_SET_FIELDS';
|
||||
export const SET_SEARCH_QUERY_STRING = 'LOGS_SET_SEARCH_QUERY_STRING';
|
||||
export const SET_SEARCH_QUERY_PARSED_PAYLOAD =
|
||||
'LOGS_SET_SEARCH_QUERY_PARSED_PAYLOAD';
|
||||
export const ADD_SEARCH_FIELD_QUERY_STRING =
|
||||
'LOGS_ADD_SEARCH_FIELD_QUERY_STRING';
|
||||
export const ADD_TO_SELECTED_FIELD = 'LOGS_ADD_TO_SELECTED_FIELD';
|
||||
export const SET_LOGS = 'LOGS_SET_LOGS';
|
||||
export const SET_LOG_LINES_PER_PAGE = 'LOGS_SET_LOG_LINES_PER_PAGE';
|
||||
export const GET_NEXT_LOG_LINES = 'LOGS_GET_NEXT_LOG_LINES';
|
||||
export const GET_PREVIOUS_LOG_LINES = 'LOGS_GET_PREVIOUS_LOG_LINES';
|
||||
export const RESET_ID_START_AND_END = 'LOGS_RESET_ID_START_AND_END';
|
||||
export const SET_LOADING = 'LOGS_SET_LOADING';
|
||||
export const SET_LOADING_AGGREGATE = 'LOGS_SET_LOADING_AGGREGATE';
|
||||
export const SET_LOGS_AGGREGATE_SERIES = 'LOGS_SET_LOGS_AGGREGATE_SERIES';
|
||||
export const SET_DETAILED_LOG_DATA = 'LOGS_SET_DETAILED_LOG_DATA';
|
||||
export const TOGGLE_LIVE_TAIL = 'LOGS_TOGGLE_LIVE_TAIL';
|
||||
export const PUSH_LIVE_TAIL_EVENT = 'LOGS_PUSH_LIVE_TAIL_EVENT';
|
||||
export const STOP_LIVE_TAIL = 'LOGS_STOP_LIVE_TAIL';
|
||||
export const FLUSH_LOGS = 'LOGS_FLUSH_LOGS';
|
||||
export const SET_LIVE_TAIL_START_TIME = 'LOGS_SET_LIVE_TAIL_START_TIME';
|
||||
export const SET_LINES_PER_ROW = 'SET_LINES_PER_ROW';
|
||||
export const SET_VIEW_MODE = 'SET_VIEW_MODE';
|
||||
export const UPDATE_SELECTED_FIELDS = 'LOGS_UPDATE_SELECTED_FIELDS';
|
||||
export const UPDATE_INTERESTING_FIELDS = 'LOGS_UPDATE_INTERESTING_FIELDS';
|
||||
export const SET_LOGS_ORDER = 'SET_LOGS_ORDER';
|
||||
|
||||
export interface GetFields {
|
||||
type: typeof GET_FIELDS;
|
||||
}
|
||||
|
||||
export interface SetFields {
|
||||
type: typeof SET_FIELDS;
|
||||
payload: IFields;
|
||||
}
|
||||
export interface SetSearchQueryString {
|
||||
type: typeof SET_SEARCH_QUERY_STRING;
|
||||
payload: {
|
||||
searchQueryString: string;
|
||||
globalTime?: GlobalTime;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SetSearchQueryParsedPayload {
|
||||
type: typeof SET_SEARCH_QUERY_PARSED_PAYLOAD;
|
||||
payload: ILogQLParsedQueryItem[];
|
||||
}
|
||||
export interface AddSearchFieldQueryString {
|
||||
type: typeof ADD_SEARCH_FIELD_QUERY_STRING;
|
||||
payload: string;
|
||||
}
|
||||
export interface AddToSelectedField {
|
||||
type: typeof ADD_TO_SELECTED_FIELD;
|
||||
payload: IFieldMoveToSelected;
|
||||
}
|
||||
|
||||
export interface UpdateLogs {
|
||||
type: typeof SET_LOGS;
|
||||
payload: ILog[];
|
||||
}
|
||||
export interface SetLogsLinesPerPage {
|
||||
type: typeof SET_LOG_LINES_PER_PAGE;
|
||||
payload: {
|
||||
logsLinesPerPage: Pagination['limit'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface PreviousLogsLines {
|
||||
type: typeof GET_PREVIOUS_LOG_LINES;
|
||||
}
|
||||
export interface NextLogsLines {
|
||||
type: typeof GET_NEXT_LOG_LINES;
|
||||
}
|
||||
export interface ResetIdStartAndEnd {
|
||||
type: typeof RESET_ID_START_AND_END;
|
||||
payload: GlobalTime;
|
||||
}
|
||||
export interface SetLoading {
|
||||
type: typeof SET_LOADING;
|
||||
payload: boolean;
|
||||
}
|
||||
export interface SetLoadingAggregate {
|
||||
type: typeof SET_LOADING_AGGREGATE;
|
||||
payload: boolean;
|
||||
}
|
||||
|
||||
export interface SetLogsAggregateSeries {
|
||||
type: typeof SET_LOGS_AGGREGATE_SERIES;
|
||||
payload: ILogsAggregate[];
|
||||
}
|
||||
export interface SetDetailedLogData {
|
||||
type: typeof SET_DETAILED_LOG_DATA;
|
||||
payload: ILog | null;
|
||||
}
|
||||
|
||||
export interface ToggleLiveTail {
|
||||
type: typeof TOGGLE_LIVE_TAIL;
|
||||
payload: TLogsLiveTailState;
|
||||
}
|
||||
export interface PushLiveTailEvent {
|
||||
type: typeof PUSH_LIVE_TAIL_EVENT;
|
||||
payload: ILog[];
|
||||
}
|
||||
export interface StopLiveTail {
|
||||
type: typeof STOP_LIVE_TAIL;
|
||||
}
|
||||
export interface FlushLogs {
|
||||
type: typeof FLUSH_LOGS;
|
||||
}
|
||||
export interface SetLiveTailStartTime {
|
||||
type: typeof SET_LIVE_TAIL_START_TIME;
|
||||
payload: number;
|
||||
}
|
||||
|
||||
export interface SetLinesPerRow {
|
||||
type: typeof SET_LINES_PER_ROW;
|
||||
payload: number;
|
||||
}
|
||||
|
||||
export interface SetViewMode {
|
||||
type: typeof SET_VIEW_MODE;
|
||||
payload: LogViewMode;
|
||||
}
|
||||
type IFieldType = 'interesting' | 'selected';
|
||||
|
||||
export interface UpdateSelectedInterestFields {
|
||||
type: typeof UPDATE_INTERESTING_FIELDS | typeof UPDATE_SELECTED_FIELDS;
|
||||
payload: {
|
||||
field: IField[];
|
||||
type: IFieldType;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SetLogsOrder {
|
||||
type: typeof SET_LOGS_ORDER;
|
||||
payload: OrderPreferenceItems;
|
||||
}
|
||||
|
||||
export type LogsActions =
|
||||
| GetFields
|
||||
| SetFields
|
||||
| SetSearchQueryString
|
||||
| SetSearchQueryParsedPayload
|
||||
| AddSearchFieldQueryString
|
||||
| AddToSelectedField
|
||||
| UpdateLogs
|
||||
| SetLogsLinesPerPage
|
||||
| PreviousLogsLines
|
||||
| NextLogsLines
|
||||
| ResetIdStartAndEnd
|
||||
| SetLoading
|
||||
| SetLoadingAggregate
|
||||
| SetLogsAggregateSeries
|
||||
| SetDetailedLogData
|
||||
| ToggleLiveTail
|
||||
| PushLiveTailEvent
|
||||
| StopLiveTail
|
||||
| FlushLogs
|
||||
| SetLiveTailStartTime
|
||||
| SetLinesPerRow
|
||||
| SetViewMode
|
||||
| UpdateSelectedInterestFields
|
||||
| SetLogsOrder;
|
||||
@@ -1,4 +0,0 @@
|
||||
import { IFieldMoveToSelected } from './fields';
|
||||
|
||||
export type Props = IFieldMoveToSelected;
|
||||
export type PayloadProps = IFieldMoveToSelected;
|
||||
@@ -1,13 +0,0 @@
|
||||
import { ILog } from './log';
|
||||
|
||||
export type PayloadProps = ILog[];
|
||||
export type Props = {
|
||||
q: string;
|
||||
limit: number;
|
||||
orderBy: string;
|
||||
order: string;
|
||||
idGt?: string;
|
||||
idLt?: string;
|
||||
timestampStart?: number;
|
||||
timestampEnd?: number;
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
export type PayloadProps = Record<
|
||||
string,
|
||||
{
|
||||
timestamp: number;
|
||||
value: number;
|
||||
}
|
||||
>;
|
||||
export type Props = {
|
||||
timestampStart: number;
|
||||
timestampEnd: number;
|
||||
step: number;
|
||||
q?: string;
|
||||
idGt?: string;
|
||||
idLt?: string;
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
import { IFields } from './fields';
|
||||
|
||||
export type PayloadProps = IFields;
|
||||
@@ -1 +0,0 @@
|
||||
export type TLogsLiveTailState = 'PLAYING' | 'PAUSED' | 'STOPPED';
|
||||
@@ -1,5 +0,0 @@
|
||||
export interface ILogsAggregate {
|
||||
timestamp: number;
|
||||
value: number;
|
||||
time: Date;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
import { Pagination } from 'hooks/queryPagination';
|
||||
import { ILogQLParsedQueryItem } from 'lib/logql/types';
|
||||
import { OrderPreferenceItems } from 'pages/Logs/config';
|
||||
import { IFields } from 'types/api/logs/fields';
|
||||
import { TLogsLiveTailState } from 'types/api/logs/liveTail';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { ILogsAggregate } from 'types/api/logs/logAggregate';
|
||||
|
||||
export interface ILogsReducer {
|
||||
fields: IFields;
|
||||
searchFilter: {
|
||||
queryString: string;
|
||||
parsedQuery: ILogQLParsedQueryItem[];
|
||||
};
|
||||
logs: ILog[];
|
||||
logLinesPerPage: Pagination['limit'];
|
||||
linesPerRow: number;
|
||||
viewMode: LogViewMode;
|
||||
idEnd: string;
|
||||
idStart: string;
|
||||
isLoading: boolean;
|
||||
isLoadingAggregate: boolean;
|
||||
logsAggregate: ILogsAggregate[];
|
||||
selectedLogId: string | null;
|
||||
detailedLog: null | ILog;
|
||||
liveTail: TLogsLiveTailState;
|
||||
liveTailStartRange: number; // time in minutes
|
||||
order: OrderPreferenceItems;
|
||||
}
|
||||
|
||||
export default ILogsReducer;
|
||||
@@ -120,7 +120,6 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
||||
TRACES_FUNNELS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
TRACES_FUNNELS_DETAIL: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
LOGS_BASE: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
OLD_LOGS_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
SHORTCUTS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
INTEGRATIONS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
INTEGRATIONS_DETAIL: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
@@ -180,7 +179,6 @@ export const routeWithInitialAuthZSupport = {
|
||||
ROLE_EDIT: true,
|
||||
SERVICE_ACCOUNTS_SETTINGS: true,
|
||||
SUPPORT: true,
|
||||
OLD_LOGS_EXPLORER: true,
|
||||
METRICS_EXPLORER: true,
|
||||
METRICS_EXPLORER_EXPLORER: true,
|
||||
METRICS_EXPLORER_VOLUME_CONTROL: true,
|
||||
|
||||
@@ -3021,10 +3021,8 @@ func (aH *APIHandler) UninstallIntegration(w http.ResponseWriter, r *http.Reques
|
||||
// logs
|
||||
func (aH *APIHandler) RegisterLogsRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
subRouter := router.PathPrefix("/api/v1/logs").Subrouter()
|
||||
subRouter.HandleFunc("", am.ViewAccess(aH.getLogs)).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/fields", am.ViewAccess(aH.logFields)).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/fields", am.EditAccess(aH.logFieldUpdate)).Methods(http.MethodPost)
|
||||
subRouter.HandleFunc("/aggregate", am.ViewAccess(aH.logAggregate)).Methods(http.MethodGet)
|
||||
|
||||
// log pipelines
|
||||
subRouter.HandleFunc("/pipelines/preview", am.ViewAccess(aH.PreviewLogsPipelinesHandler)).Methods(http.MethodPost)
|
||||
@@ -3064,14 +3062,6 @@ func (aH *APIHandler) logFieldUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
aH.WriteJSON(w, r, field)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getLogs(w http.ResponseWriter, r *http.Request) {
|
||||
aH.WriteJSON(w, r, map[string]interface{}{"results": []interface{}{}})
|
||||
}
|
||||
|
||||
func (aH *APIHandler) logAggregate(w http.ResponseWriter, r *http.Request) {
|
||||
aH.WriteJSON(w, r, model.GetLogsAggregatesResponse{})
|
||||
}
|
||||
|
||||
func parseAgentConfigVersion(r *http.Request) (int, error) {
|
||||
versionString := mux.Vars(r)["version"]
|
||||
|
||||
|
||||
@@ -502,21 +502,6 @@ type SignozLog struct {
|
||||
Attributes_float64 map[string]float64 `json:"attributes_float" ch:"attributes_float64"`
|
||||
Attributes_bool map[string]bool `json:"attributes_bool" ch:"attributes_bool"`
|
||||
}
|
||||
type GetLogsAggregatesResponse struct {
|
||||
Items map[int64]LogsAggregatesResponseItem `json:"items"`
|
||||
}
|
||||
|
||||
type LogsAggregatesResponseItem struct {
|
||||
Timestamp int64 `json:"timestamp,omitempty" `
|
||||
Value interface{} `json:"value,omitempty"`
|
||||
GroupBy map[string]interface{} `json:"groupBy,omitempty"`
|
||||
}
|
||||
|
||||
type LogsAggregatesDBResponseItem struct {
|
||||
Timestamp int64 `ch:"ts_start_interval"`
|
||||
Value float64 `ch:"value"`
|
||||
GroupBy string `ch:"groupBy"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (s *ServiceItem) MarshalJSON() ([]byte, error) {
|
||||
|
||||
Reference in New Issue
Block a user