From 28cfeb70ce6bd393a494064bfd8c5428d697f2f2 Mon Sep 17 00:00:00 2001 From: ahmadshaheer Date: Thu, 16 Jan 2025 16:31:47 +0430 Subject: [PATCH 1/3] feat: show/hide timestamp and body fields in logs explorer (raw, default, column views) --- .../src/components/Logs/ListLogView/index.tsx | 50 ++++---- .../src/components/Logs/RawLogView/index.tsx | 45 +++++-- .../Logs/TableView/useTableView.tsx | 114 ++++++++++-------- .../InfinityTableView/index.tsx | 36 +++--- .../src/container/OptionsMenu/constants.ts | 21 +++- .../container/OptionsMenu/useOptionsMenu.ts | 22 +++- 6 files changed, 180 insertions(+), 108 deletions(-) diff --git a/frontend/src/components/Logs/ListLogView/index.tsx b/frontend/src/components/Logs/ListLogView/index.tsx index 1a4c7b4ff31..b30353696fd 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 8b17590a977..897dbe98a79 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, - ); - - return `${date} | ${attributesText} ${data.body}`; + const parts = []; + + // 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 e320548605d..9971f6d775a 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/container/LogsExplorerList/InfinityTableView/index.tsx b/frontend/src/container/LogsExplorerList/InfinityTableView/index.tsx index eec6cc032a1..9aa982abe70 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'; - - return ( - - {(column.title as string).replace(/^\w/, (c) => c.toUpperCase())} - - ); - })} + {tableColumns + .filter((column) => column.key) + .map((column) => { + const isDragColumn = column.key !== 'expand'; + + return ( + + {(column.title as string).replace(/^\w/, (c) => c.toUpperCase())} + + ); + })} ), [tableColumns, isDarkMode, tableViewProps?.fontSize], diff --git a/frontend/src/container/OptionsMenu/constants.ts b/frontend/src/container/OptionsMenu/constants.ts index 153981f3c64..7bf6a007d1d 100644 --- a/frontend/src/container/OptionsMenu/constants.ts +++ b/frontend/src/container/OptionsMenu/constants.ts @@ -5,7 +5,26 @@ import { FontSize, OptionsQuery } from './types'; export const URL_OPTIONS = 'options'; export const defaultOptionsQuery: OptionsQuery = { - selectColumns: [], + selectColumns: [ + { + 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, + }, + ], maxLines: 2, format: 'raw', fontSize: FontSize.SMALL, diff --git a/frontend/src/container/OptionsMenu/useOptionsMenu.ts b/frontend/src/container/OptionsMenu/useOptionsMenu.ts index a4a91d82f42..e81fca43aa4 100644 --- a/frontend/src/container/OptionsMenu/useOptionsMenu.ts +++ b/frontend/src/container/OptionsMenu/useOptionsMenu.ts @@ -169,6 +169,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 [ + ...defaultOptionsQuery.selectColumns, + ...searchedAttributesData.payload.attributeKeys.filter( + (attribute) => attribute.key !== 'body', + ), + ]; + } return searchedAttributesData.payload.attributeKeys; } if (dataSource === DataSource.TRACES) { @@ -198,12 +207,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) => { From ec7b7610e56f71acddd784dedf4769a6356c520c Mon Sep 17 00:00:00 2001 From: ahmadshaheer Date: Sun, 19 Jan 2025 11:08:53 +0430 Subject: [PATCH 2/3] fix: add width to log indicator column to ensure that a single column doesn't take half the space --- .../src/container/LogsExplorerList/InfinityTableView/styles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/container/LogsExplorerList/InfinityTableView/styles.ts b/frontend/src/container/LogsExplorerList/InfinityTableView/styles.ts index 9de75e66426..a22e7a4cc0c 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}; `; From 3953fb1d9a5615fe90dc2ae5245d38c1b61995fe Mon Sep 17 00:00:00 2001 From: ahmadshaheer Date: Thu, 23 Jan 2025 08:51:58 +0430 Subject: [PATCH 3/3] fix: handle edge cases and fix issues for show/hide body and timestamp in logs explorer --- .../LogsFormatOptionsMenu.tsx | 12 +++--- .../ExplorerOptions/ExplorerOptions.tsx | 21 +++++++--- .../src/container/OptionsMenu/constants.ts | 42 ++++++++++--------- frontend/src/container/OptionsMenu/types.ts | 1 + .../container/OptionsMenu/useOptionsMenu.ts | 3 +- frontend/src/pages/LogsExplorer/index.tsx | 42 ++++++++++++++++++- 6 files changed, 89 insertions(+), 32 deletions(-) diff --git a/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx b/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx index 740ceaf5b7f..91d6aa30aae 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 85ce464db48..8645894dc2f 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/OptionsMenu/constants.ts b/frontend/src/container/OptionsMenu/constants.ts index 7bf6a007d1d..2e3c20e7c74 100644 --- a/frontend/src/container/OptionsMenu/constants.ts +++ b/frontend/src/container/OptionsMenu/constants.ts @@ -5,31 +5,33 @@ import { FontSize, OptionsQuery } from './types'; export const URL_OPTIONS = 'options'; export const defaultOptionsQuery: OptionsQuery = { - selectColumns: [ - { - 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, - }, - ], + selectColumns: [], maxLines: 2, format: 'raw', 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 2c57d66b283..b2382cd487d 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 e81fca43aa4..93f99348fbd 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, @@ -172,7 +173,7 @@ const useOptionsMenu = ({ if (dataSource === DataSource.LOGS) { // add timestamp and body to the list of attributes return [ - ...defaultOptionsQuery.selectColumns, + ...defaultLogsSelectedColumns, ...searchedAttributesData.payload.attributeKeys.filter( (attribute) => attribute.key !== 'body', ), diff --git a/frontend/src/pages/LogsExplorer/index.tsx b/frontend/src/pages/LogsExplorer/index.tsx index 5e4d1cf55f7..bbcbae311eb 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 ||