diff --git a/frontend/src/components/Logs/ListLogView/index.tsx b/frontend/src/components/Logs/ListLogView/index.tsx
index 1a4c7b4ff3..b30353696f 100644
--- a/frontend/src/components/Logs/ListLogView/index.tsx
+++ b/frontend/src/components/Logs/ListLogView/index.tsx
@@ -220,12 +220,14 @@ function ListLogView({
-
+ {updatedSelecedFields.some((field) => field.name === 'body') && (
+
+ )}
{flattenLogData.stream && (
)}
-
-
- {updatedSelecedFields.map((field) =>
- isValidLogField(flattenLogData[field.name] as never) ? (
-
- ) : null,
+ {updatedSelecedFields.some((field) => field.name === 'timestamp') && (
+
)}
+
+ {updatedSelecedFields
+ .filter((field) => !['timestamp', 'body'].includes(field.name))
+ .map((field) =>
+ isValidLogField(flattenLogData[field.name] as never) ? (
+
+ ) : null,
+ )}
diff --git a/frontend/src/components/Logs/RawLogView/index.tsx b/frontend/src/components/Logs/RawLogView/index.tsx
index 8b17590a97..897dbe98a7 100644
--- a/frontend/src/components/Logs/RawLogView/index.tsx
+++ b/frontend/src/components/Logs/RawLogView/index.tsx
@@ -74,6 +74,7 @@ function RawLogView({
);
const attributesValues = updatedSelecedFields
+ .filter((field) => !['timestamp', 'body'].includes(field.name))
.map((field) => flattenLogData[field.name])
.filter((attribute) => {
// loadash isEmpty doesnot work with numbers
@@ -93,22 +94,40 @@ function RawLogView({
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const text = useMemo(() => {
- const date =
- typeof data.timestamp === 'string'
- ? formatTimezoneAdjustedTimestamp(
- data.timestamp,
- DATE_TIME_FORMATS.ISO_DATETIME_MS,
- )
- : formatTimezoneAdjustedTimestamp(
- data.timestamp / 1e6,
- DATE_TIME_FORMATS.ISO_DATETIME_MS,
- );
+ const parts = [];
- return `${date} | ${attributesText} ${data.body}`;
+ // Check if timestamp is selected
+ const showTimestamp = selectedFields.some(
+ (field) => field.name === 'timestamp',
+ );
+ if (showTimestamp) {
+ const date =
+ typeof data.timestamp === 'string'
+ ? formatTimezoneAdjustedTimestamp(
+ data.timestamp,
+ DATE_TIME_FORMATS.ISO_DATETIME_MS,
+ )
+ : formatTimezoneAdjustedTimestamp(
+ data.timestamp / 1e6,
+ DATE_TIME_FORMATS.ISO_DATETIME_MS,
+ );
+ parts.push(date);
+ }
+
+ // Check if body is selected
+ const showBody = selectedFields.some((field) => field.name === 'body');
+ if (showBody) {
+ parts.push(`${attributesText} ${data.body}`);
+ } else {
+ parts.push(attributesText);
+ }
+
+ return parts.join(' | ');
}, [
+ selectedFields,
+ attributesText,
data.timestamp,
data.body,
- attributesText,
formatTimezoneAdjustedTimestamp,
]);
diff --git a/frontend/src/components/Logs/TableView/useTableView.tsx b/frontend/src/components/Logs/TableView/useTableView.tsx
index e320548605..9971f6d775 100644
--- a/frontend/src/components/Logs/TableView/useTableView.tsx
+++ b/frontend/src/components/Logs/TableView/useTableView.tsx
@@ -49,7 +49,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
const columns: ColumnsType> = useMemo(() => {
const fieldColumns: ColumnsType> = fields
- .filter((e) => e.name !== 'id')
+ .filter((e) => !['id', 'body', 'timestamp'].includes(e.name))
.map(({ name }) => ({
title: name,
dataIndex: name,
@@ -92,58 +92,70 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
),
}),
},
- {
- title: 'timestamp',
- dataIndex: 'timestamp',
- key: 'timestamp',
- // https://github.com/ant-design/ant-design/discussions/36886
- render: (field): ColumnTypeRender> => {
- const date =
- typeof field === 'string'
- ? formatTimezoneAdjustedTimestamp(
- field,
- DATE_TIME_FORMATS.ISO_DATETIME_MS,
- )
- : formatTimezoneAdjustedTimestamp(
- field / 1e6,
- DATE_TIME_FORMATS.ISO_DATETIME_MS,
- );
- return {
- children: (
-
-
- {date}
-
-
- ),
- };
- },
- },
+ ...(fields.some((field) => field.name === 'timestamp')
+ ? [
+ {
+ title: 'timestamp',
+ dataIndex: 'timestamp',
+ key: 'timestamp',
+ // https://github.com/ant-design/ant-design/discussions/36886
+ render: (
+ field: string | number,
+ ): ColumnTypeRender> => {
+ const date =
+ typeof field === 'string'
+ ? formatTimezoneAdjustedTimestamp(
+ field,
+ DATE_TIME_FORMATS.ISO_DATETIME_MS,
+ )
+ : formatTimezoneAdjustedTimestamp(
+ field / 1e6,
+ DATE_TIME_FORMATS.ISO_DATETIME_MS,
+ );
+ return {
+ children: (
+
+
+ {date}
+
+
+ ),
+ };
+ },
+ },
+ ]
+ : []),
...(appendTo === 'center' ? fieldColumns : []),
- {
- title: 'body',
- dataIndex: 'body',
- key: 'body',
- render: (field): ColumnTypeRender> => ({
- props: {
- style: defaultTableStyle,
- },
- children: (
- field.name === 'body')
+ ? [
+ {
+ title: 'body',
+ dataIndex: 'body',
+ key: 'body',
+ render: (
+ field: string | number,
+ ): ColumnTypeRender> => ({
+ props: {
+ style: defaultTableStyle,
+ },
+ children: (
+
),
- }}
- fontSize={fontSize}
- linesPerRow={linesPerRow}
- isDarkMode={isDarkMode}
- />
- ),
- }),
- },
+ }),
+ },
+ ]
+ : []),
...(appendTo === 'end' ? fieldColumns : []),
];
}, [
diff --git a/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx b/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx
index 740ceaf5b7..91d6aa30aa 100644
--- a/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx
+++ b/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx
@@ -417,11 +417,13 @@ export default function LogsFormatOptionsMenu({
{key}
- addColumn.onRemove(id as string)}
- />
+ {addColumn?.value?.length > 1 && (
+ addColumn.onRemove(id as string)}
+ />
+ )}
))}
{addColumn && addColumn?.value?.length === 0 && (
diff --git a/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx b/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx
index 85ce464db4..8645894dc2 100644
--- a/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx
+++ b/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx
@@ -37,7 +37,7 @@ import useErrorNotification from 'hooks/useErrorNotification';
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
import { useNotifications } from 'hooks/useNotifications';
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
-import { cloneDeep, isEqual } from 'lodash-es';
+import { cloneDeep, isEqual, omit } from 'lodash-es';
import {
Check,
ConciergeBell,
@@ -256,13 +256,17 @@ function ExplorerOptions({
const { handleExplorerTabChange } = useHandleExplorerTabChange();
const { options, handleOptionsChange } = useOptionsMenu({
- storageKey: LOCALSTORAGE.TRACES_LIST_OPTIONS,
- dataSource: DataSource.TRACES,
+ storageKey:
+ sourcepage === DataSource.TRACES
+ ? LOCALSTORAGE.TRACES_LIST_OPTIONS
+ : LOCALSTORAGE.LOGS_LIST_OPTIONS,
+ dataSource: sourcepage,
aggregateOperator: StringOperators.NOOP,
});
type ExtraData = {
selectColumns?: BaseAutocompleteData[];
+ version?: number;
};
const updateOrRestoreSelectColumns = (
@@ -283,14 +287,20 @@ function ExplorerOptions({
console.error('Error parsing extraData:', error);
}
+ let backwardCompatibleOptions = options;
+
+ if (!extraData?.version) {
+ backwardCompatibleOptions = omit(options, 'version');
+ }
+
if (extraData.selectColumns?.length) {
handleOptionsChange({
- ...options,
+ ...backwardCompatibleOptions,
selectColumns: extraData.selectColumns,
});
} else if (!isEqual(defaultTraceSelectedColumns, options.selectColumns)) {
handleOptionsChange({
- ...options,
+ ...backwardCompatibleOptions,
selectColumns: defaultTraceSelectedColumns,
});
}
@@ -423,6 +433,7 @@ function ExplorerOptions({
extraData: JSON.stringify({
color,
selectColumns: options.selectColumns,
+ version: 1,
}),
notifications,
panelType: panelType || PANEL_TYPES.LIST,
diff --git a/frontend/src/container/LogsExplorerList/InfinityTableView/index.tsx b/frontend/src/container/LogsExplorerList/InfinityTableView/index.tsx
index eec6cc032a..9aa982abe7 100644
--- a/frontend/src/container/LogsExplorerList/InfinityTableView/index.tsx
+++ b/frontend/src/container/LogsExplorerList/InfinityTableView/index.tsx
@@ -121,23 +121,25 @@ const InfinityTable = forwardRef(
const tableHeader = useCallback(
() => (
- {tableColumns.map((column) => {
- const isDragColumn = column.key !== 'expand';
+ {tableColumns
+ .filter((column) => column.key)
+ .map((column) => {
+ const isDragColumn = column.key !== 'expand';
- return (
-
- {(column.title as string).replace(/^\w/, (c) => c.toUpperCase())}
-
- );
- })}
+ return (
+
+ {(column.title as string).replace(/^\w/, (c) => c.toUpperCase())}
+
+ );
+ })}
),
[tableColumns, isDarkMode, tableViewProps?.fontSize],
diff --git a/frontend/src/container/LogsExplorerList/InfinityTableView/styles.ts b/frontend/src/container/LogsExplorerList/InfinityTableView/styles.ts
index 9de75e6642..a22e7a4cc0 100644
--- a/frontend/src/container/LogsExplorerList/InfinityTableView/styles.ts
+++ b/frontend/src/container/LogsExplorerList/InfinityTableView/styles.ts
@@ -29,7 +29,7 @@ export const TableCellStyled = styled.td`
props.$isDarkMode ? 'inherit' : themeColors.whiteCream};
${({ $isLogIndicator }): string =>
- $isLogIndicator ? 'padding: 0 0 0 8px;' : ''}
+ $isLogIndicator ? 'padding: 0 0 0 8px;width: 15px;' : ''}
color: ${(props): string =>
props.$isDarkMode ? themeColors.white : themeColors.bckgGrey};
`;
diff --git a/frontend/src/container/OptionsMenu/constants.ts b/frontend/src/container/OptionsMenu/constants.ts
index 153981f3c6..2e3c20e7c7 100644
--- a/frontend/src/container/OptionsMenu/constants.ts
+++ b/frontend/src/container/OptionsMenu/constants.ts
@@ -11,6 +11,27 @@ export const defaultOptionsQuery: OptionsQuery = {
fontSize: FontSize.SMALL,
};
+export const defaultLogsSelectedColumns = [
+ {
+ key: 'timestamp',
+ dataType: DataTypes.String,
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'timestamp--string--tag--true',
+ isIndexed: false,
+ },
+ {
+ key: 'body',
+ dataType: DataTypes.String,
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'body--string--tag--true',
+ isIndexed: false,
+ },
+];
+
export const defaultTraceSelectedColumns = [
{
key: 'serviceName',
diff --git a/frontend/src/container/OptionsMenu/types.ts b/frontend/src/container/OptionsMenu/types.ts
index 2c57d66b28..b2382cd487 100644
--- a/frontend/src/container/OptionsMenu/types.ts
+++ b/frontend/src/container/OptionsMenu/types.ts
@@ -17,6 +17,7 @@ export interface OptionsQuery {
maxLines: number;
format: LogViewMode;
fontSize: FontSize;
+ version?: number;
}
export interface InitialOptions
diff --git a/frontend/src/container/OptionsMenu/useOptionsMenu.ts b/frontend/src/container/OptionsMenu/useOptionsMenu.ts
index a4a91d82f4..93f99348fb 100644
--- a/frontend/src/container/OptionsMenu/useOptionsMenu.ts
+++ b/frontend/src/container/OptionsMenu/useOptionsMenu.ts
@@ -21,6 +21,7 @@ import {
import { DataSource } from 'types/common/queryBuilder';
import {
+ defaultLogsSelectedColumns,
defaultOptionsQuery,
defaultTraceSelectedColumns,
URL_OPTIONS,
@@ -169,6 +170,15 @@ const useOptionsMenu = ({
const searchedAttributeKeys = useMemo(() => {
if (searchedAttributesData?.payload?.attributeKeys?.length) {
+ if (dataSource === DataSource.LOGS) {
+ // add timestamp and body to the list of attributes
+ return [
+ ...defaultLogsSelectedColumns,
+ ...searchedAttributesData.payload.attributeKeys.filter(
+ (attribute) => attribute.key !== 'body',
+ ),
+ ];
+ }
return searchedAttributesData.payload.attributeKeys;
}
if (dataSource === DataSource.TRACES) {
@@ -198,12 +208,17 @@ const useOptionsMenu = ({
);
const optionsFromAttributeKeys = useMemo(() => {
- const filteredAttributeKeys = searchedAttributeKeys.filter(
- (item) => item.key !== 'body',
- );
+ const filteredAttributeKeys = searchedAttributeKeys.filter((item) => {
+ // For other data sources, only filter out 'body' if it exists
+ if (dataSource !== DataSource.LOGS) {
+ return item.key !== 'body';
+ }
+ // For LOGS, keep all keys
+ return true;
+ });
return getOptionsFromKeys(filteredAttributeKeys, selectedColumnKeys);
- }, [searchedAttributeKeys, selectedColumnKeys]);
+ }, [dataSource, searchedAttributeKeys, selectedColumnKeys]);
const handleRedirectWithOptionsData = useCallback(
(newQueryData: OptionsQuery) => {
diff --git a/frontend/src/pages/LogsExplorer/index.tsx b/frontend/src/pages/LogsExplorer/index.tsx
index 5e4d1cf55f..bbcbae311e 100644
--- a/frontend/src/pages/LogsExplorer/index.tsx
+++ b/frontend/src/pages/LogsExplorer/index.tsx
@@ -9,11 +9,18 @@ import QuickFilters from 'components/QuickFilters/QuickFilters';
import { LOCALSTORAGE } from 'constants/localStorage';
import LogExplorerQuerySection from 'container/LogExplorerQuerySection';
import LogsExplorerViews from 'container/LogsExplorerViews';
+import {
+ defaultLogsSelectedColumns,
+ defaultOptionsQuery,
+ URL_OPTIONS,
+} from 'container/OptionsMenu/constants';
+import { OptionsQuery } from 'container/OptionsMenu/types';
import LeftToolbarActions from 'container/QueryBuilder/components/ToolbarActions/LeftToolbarActions';
import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions';
import Toolbar from 'container/Toolbar/Toolbar';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
-import { isNull } from 'lodash-es';
+import useUrlQueryData from 'hooks/useUrlQueryData';
+import { isEqual, isNull } from 'lodash-es';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import { useEffect, useMemo, useRef, useState } from 'react';
import { DataSource } from 'types/common/queryBuilder';
@@ -73,6 +80,39 @@ function LogsExplorer(): JSX.Element {
}
}, [currentQuery.builder.queryData, currentQuery.builder.queryData.length]);
+ const {
+ queryData: optionsQueryData,
+ redirectWithQuery: redirectWithOptionsData,
+ } = useUrlQueryData(URL_OPTIONS, defaultOptionsQuery);
+
+ const migrateOptionsQuery = (query: OptionsQuery): OptionsQuery => {
+ // If version is missing AND timestamp/body are not in selectColumns, this is an old URL
+ if (
+ !query.version &&
+ !query.selectColumns.some((col) => col.key === 'timestamp') &&
+ !query.selectColumns.some((col) => col.key === 'body')
+ ) {
+ return {
+ ...query,
+ version: 1,
+ selectColumns: [
+ // Add default timestamp and body columns
+ ...defaultLogsSelectedColumns,
+ ...query.selectColumns,
+ ],
+ };
+ }
+ return query;
+ };
+
+ useEffect(() => {
+ const migratedQuery = migrateOptionsQuery(optionsQueryData);
+ // Only redirect if the query was actually modified
+ if (!isEqual(migratedQuery, optionsQueryData)) {
+ redirectWithOptionsData(migratedQuery);
+ }
+ }, [optionsQueryData, redirectWithOptionsData]);
+
const isMultipleQueries = useMemo(
() =>
currentQuery.builder.queryData?.length > 1 ||