Compare commits

...

2 Commits

Author SHA1 Message Date
therealpandey
3de6f2e72c refactor(frontend): remove the old logs explorer
The page and its redux slice were the only callers of the logs list and
aggregate endpoints removed in the previous commit, so it rendered
nothing. Dropping it takes the logs store, the logql parser and the
legacy log api clients with it.

LogViewMode moves to container/OptionsMenu/types and the restricted-field
constants to container/LogDetailedView/config, their only remaining
consumers.
2026-09-18 22:25:03 +05:30
therealpandey
be9170ced6 refactor: remove stubbed logs list and aggregate endpoints
GET /api/v1/logs and GET /api/v1/logs/aggregate have returned empty
payloads since #8299; drop the routes and the response models that only
existed to serve them.
2026-09-18 22:24:51 +05:30
99 changed files with 40 additions and 4026 deletions

View File

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

View File

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

View File

@@ -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'),
);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,
},
);

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,
});

View File

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

View File

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

View File

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

View File

@@ -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,
]);

View File

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

View File

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

View File

@@ -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',
},
];

View File

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

View File

@@ -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'};
`;

View File

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

View File

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

View File

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

View File

@@ -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] },
};

View File

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

View File

@@ -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]};
`;

View File

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

View File

@@ -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),
);
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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]};
}
`;

View File

@@ -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),
)}`;
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +0,0 @@
.logs-card {
flex: 1;
}

View File

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

View File

@@ -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',
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,
);
});
});
});

View File

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

View File

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

View File

@@ -1,7 +0,0 @@
export class ErrorConvertToFullText extends Error {
constructor(message: string) {
super(message);
this.name = 'ErrorConvertToFullText';
Object.setPrototypeOf(this, ErrorConvertToFullText.prototype);
}
}

View File

@@ -1,7 +0,0 @@
export class ErrorInvalidQueryPair extends Error {
constructor(message: string) {
super(message);
this.name = 'ErrorInvalidQueryPair';
Object.setPrototypeOf(this, ErrorInvalidQueryPair.prototype);
}
}

View File

@@ -1,2 +0,0 @@
export * from './ConvertToFullText';
export * from './InvalidQueryPair';

View File

@@ -1,4 +0,0 @@
export * from './parser';
export * from './reverseParser';
export * from './splitter';
export * from './tokens';

View File

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

View File

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

View File

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

View File

@@ -1,4 +0,0 @@
export interface ILogQLParsedQueryItem {
type: string;
value: string | string[];
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +0,0 @@
.logs-col-container {
display: flex;
flex-direction: column;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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),
),
},
});
}
};

View File

@@ -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,
});
};

View File

@@ -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,
});
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +0,0 @@
import { IFieldMoveToSelected } from './fields';
export type Props = IFieldMoveToSelected;
export type PayloadProps = IFieldMoveToSelected;

View File

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

View File

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

View File

@@ -1,3 +0,0 @@
import { IFields } from './fields';
export type PayloadProps = IFields;

View File

@@ -1 +0,0 @@
export type TLogsLiveTailState = 'PLAYING' | 'PAUSED' | 'STOPPED';

View File

@@ -1,5 +0,0 @@
export interface ILogsAggregate {
timestamp: number;
value: number;
time: Date;
}

View File

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

View File

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

View File

@@ -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"]

View File

@@ -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) {