Merge remote-tracking branch 'origin/main' into feat/mt-apiextensions

This commit is contained in:
Igor Suleymanov
2025-12-12 18:04:20 +02:00
9 changed files with 90 additions and 22 deletions
@@ -119,6 +119,16 @@ export function colorIdEnumToColorIdV2(colorId: FieldColorModeIdV1 | string): Fi
return 'continuous-greens';
case FieldColorModeIdV1.ContinuousPurples:
return 'continuous-purples';
case FieldColorModeIdV1.ContinuousViridis:
return 'continuous-viridis';
case FieldColorModeIdV1.ContinuousMagma:
return 'continuous-magma';
case FieldColorModeIdV1.ContinuousPlasma:
return 'continuous-plasma';
case FieldColorModeIdV1.ContinuousInferno:
return 'continuous-inferno';
case FieldColorModeIdV1.ContinuousCividis:
return 'continuous-cividis';
case FieldColorModeIdV1.Fixed:
return 'fixed';
case FieldColorModeIdV1.Shades:
@@ -1268,6 +1268,16 @@ function colorIdToEnumv1(colorId: FieldColorModeId): FieldColorModeIdV1 {
return FieldColorModeIdV1.ContinuousGreens;
case 'continuous-purples':
return FieldColorModeIdV1.ContinuousPurples;
case 'continuous-viridis':
return FieldColorModeIdV1.ContinuousViridis;
case 'continuous-magma':
return FieldColorModeIdV1.ContinuousMagma;
case 'continuous-plasma':
return FieldColorModeIdV1.ContinuousPlasma;
case 'continuous-inferno':
return FieldColorModeIdV1.ContinuousInferno;
case 'continuous-cividis':
return FieldColorModeIdV1.ContinuousCividis;
case 'fixed':
return FieldColorModeIdV1.Fixed;
case 'shades':
@@ -19,7 +19,7 @@ import { t } from '@grafana/i18n';
import { reportInteraction } from '@grafana/runtime';
import { getDragStyles, InlineField, Select, useStyles2 } from '@grafana/ui';
import {
getSidebarWidth,
getFieldSelectorWidth,
LogsTableFieldSelector,
MIN_WIDTH,
} from 'app/features/logs/components/fieldSelector/FieldSelector';
@@ -279,7 +279,7 @@ export function LogsTableWrap(props: Props) {
// The panel state is updated when the user interacts with the multi-select sidebar
}, [currentDataFrame, getColumnsFromProps]);
const [sidebarWidth, setSidebarWidth] = useState(getSidebarWidth(SETTING_KEY_ROOT));
const [sidebarWidth, setSidebarWidth] = useState(getFieldSelectorWidth(SETTING_KEY_ROOT));
const tableWidth = props.width - sidebarWidth;
const styles = useStyles2(getStyles, height, sidebarWidth);
@@ -35,7 +35,7 @@ export const LogListFieldSelector = ({ containerElement, dataFrames, logs }: Log
const { displayedFields, onClickShowField, onClickHideField, setDisplayedFields, logOptionsStorageKey } =
useLogListContext();
const [sidebarHeight, setSidebarHeight] = useState(220);
const [sidebarWidth, setSidebarWidth] = useState(getSidebarWidth(logOptionsStorageKey));
const [sidebarWidth, setSidebarWidth] = useState(getFieldSelectorWidth(logOptionsStorageKey));
const dragStyles = useStyles2(getDragStyles);
useLayoutEffect(() => {
@@ -74,7 +74,7 @@ export const LogListFieldSelector = ({ containerElement, dataFrames, logs }: Log
}, [setSidebarWidthWrapper]);
const expand = useCallback(() => {
const width = getSidebarWidth(logOptionsStorageKey);
const width = getFieldSelectorWidth(logOptionsStorageKey);
setSidebarWidthWrapper(width < 2 * MIN_WIDTH ? DEFAULT_WIDTH : width);
reportInteraction('logs_field_selector_expand_clicked', {
mode: 'logs',
@@ -205,7 +205,7 @@ export const LogsTableFieldSelector = ({
}, [setSidebarWidthWrapper]);
const expand = useCallback(() => {
const width = getSidebarWidth(SETTING_KEY_ROOT);
const width = getFieldSelectorWidth(SETTING_KEY_ROOT);
setSidebarWidthWrapper(width < 2 * MIN_WIDTH ? DEFAULT_WIDTH : width);
reportInteraction('logs_field_selector_expand_clicked', {
mode: 'table',
@@ -436,7 +436,7 @@ function getSuggestedFields(logs: LogListModel[], displayedFields: string[], def
return suggestedFields;
}
export function getSidebarWidth(logOptionsStorageKey?: string): number {
export function getFieldSelectorWidth(logOptionsStorageKey?: string): number {
const width =
(logOptionsStorageKey
? parseInt(store.get(`${logOptionsStorageKey}.fieldSelector.width`) ?? DEFAULT_WIDTH, 10)
@@ -445,7 +445,7 @@ export function getSidebarWidth(logOptionsStorageKey?: string): number {
return width < MIN_WIDTH ? MIN_WIDTH : width;
}
export function getSidebarState(logOptionsStorageKey?: string): boolean | undefined {
export function getFieldSelectorState(logOptionsStorageKey?: string): boolean | undefined {
if (!logOptionsStorageKey) {
return undefined;
}
@@ -3,7 +3,7 @@ import { createContext, ReactNode, useCallback, useContext, useEffect, useState
import { LogRowModel, store } from '@grafana/data';
import { getSidebarWidth } from '../fieldSelector/FieldSelector';
import { getFieldSelectorWidth } from '../fieldSelector/FieldSelector';
import { LogLineDetailsMode } from './LogLineDetails';
import { LogListModel } from './processing';
@@ -56,6 +56,7 @@ export interface Props {
logs: LogRowModel[];
logOptionsStorageKey?: string;
showControls: boolean;
showFieldSelector?: boolean;
}
export const LogDetailsContextProvider = ({
@@ -68,12 +69,13 @@ export const LogDetailsContextProvider = ({
: getDefaultDetailsMode(containerElement),
logs,
showControls,
showFieldSelector,
}: Props) => {
const [showDetails, setShowDetails] = useState<LogListModel[]>([]);
const [currentLog, setCurrentLog] = useState<LogListModel | undefined>(undefined);
const [detailsWidth, setDetailsWidthState] = useState(
getDetailsWidth(containerElement, logOptionsStorageKey, undefined, detailsModeProp, showControls)
getDetailsWidth(containerElement, logOptionsStorageKey, undefined, detailsModeProp, showControls, showFieldSelector)
);
const [detailsMode, setDetailsMode] = useState<LogLineDetailsMode>(
detailsModeProp ?? getDefaultDetailsMode(containerElement)
@@ -101,8 +103,10 @@ export const LogDetailsContextProvider = ({
// Sync log details inline and sidebar width
useEffect(() => {
setDetailsWidthState(getDetailsWidth(containerElement, logOptionsStorageKey, undefined, detailsMode, showControls));
}, [containerElement, detailsMode, logOptionsStorageKey, showControls]);
setDetailsWidthState(
getDetailsWidth(containerElement, logOptionsStorageKey, undefined, detailsMode, showControls, showFieldSelector)
);
}, [containerElement, detailsMode, logOptionsStorageKey, showControls, showFieldSelector]);
// Sync log details width
useEffect(() => {
@@ -111,13 +115,20 @@ export const LogDetailsContextProvider = ({
}
const handleResize = debounce(() => {
setDetailsWidthState((detailsWidth) =>
getDetailsWidth(containerElement, logOptionsStorageKey, detailsWidth, detailsMode, showControls)
getDetailsWidth(
containerElement,
logOptionsStorageKey,
detailsWidth,
detailsMode,
showControls,
showFieldSelector
)
);
}, 50);
const observer = new ResizeObserver(() => handleResize());
observer.observe(containerElement);
return () => observer.disconnect();
}, [containerElement, detailsMode, logOptionsStorageKey, showControls, showDetails]);
}, [containerElement, detailsMode, logOptionsStorageKey, showControls, showDetails, showFieldSelector]);
const closeDetails = useCallback(() => {
showDetails.forEach((log) => removeDetailsScrollPosition(log));
@@ -158,7 +169,10 @@ export const LogDetailsContextProvider = ({
return;
}
const maxWidth = containerElement.clientWidth - getSidebarWidth(logOptionsStorageKey) - LOG_LIST_MIN_WIDTH;
const maxWidth =
containerElement.clientWidth -
(showFieldSelector ? getFieldSelectorWidth(logOptionsStorageKey) : 0) -
LOG_LIST_MIN_WIDTH;
if (width > maxWidth) {
return;
}
@@ -166,7 +180,7 @@ export const LogDetailsContextProvider = ({
store.set(`${logOptionsStorageKey}.detailsWidth`, width);
setDetailsWidthState(width);
},
[containerElement, logOptionsStorageKey]
[containerElement, logOptionsStorageKey, showFieldSelector]
);
return (
@@ -196,12 +210,14 @@ export function getDetailsWidth(
logOptionsStorageKey?: string,
currentWidth?: number,
detailsMode: LogLineDetailsMode = 'sidebar',
showControls?: boolean
showControls?: boolean,
showFieldSelector?: boolean
) {
if (!containerElement) {
return 0;
}
const availableWidth = containerElement.clientWidth - getSidebarWidth(logOptionsStorageKey);
const availableWidth =
containerElement.clientWidth - (showFieldSelector ? getFieldSelectorWidth(logOptionsStorageKey) : 0);
if (detailsMode === 'inline') {
return availableWidth - getScrollbarWidth() - (showControls ? LOG_LIST_CONTROLS_WIDTH : 0);
}
@@ -20,6 +20,7 @@ import { setPluginLinksHook } from '@grafana/runtime';
import { createTempoDatasource } from 'app/plugins/datasource/tempo/test/mocks';
import { LOG_LINE_BODY_FIELD_NAME } from '../LogDetailsBody';
import { getFieldSelectorWidth } from '../fieldSelector/FieldSelector';
import { createLogLine } from '../mocks/logRow';
import { emptyContextData, LogDetailsContext, LogDetailsContextData } from './LogDetailsContext';
@@ -27,6 +28,10 @@ import { LogLineDetails, Props } from './LogLineDetails';
import { LogListContext, LogListContextData } from './LogListContext';
import { defaultValue } from './__mocks__/LogListContext';
jest.mock('../fieldSelector/FieldSelector');
jest.mocked(getFieldSelectorWidth).mockReturnValue(220);
jest.mock('@grafana/assistant', () => {
return {
...jest.requireActual('@grafana/assistant'),
@@ -79,6 +84,7 @@ const setup = (
},
timeZone: 'browser',
showControls: true,
showFieldSelector: true,
...(propOverrides || {}),
};
@@ -775,4 +781,24 @@ describe('LogLineDetails', () => {
expect(screen.getByText('value')).toBeInTheDocument();
expect(screen.getByText('Open service overview for label')).toBeInTheDocument();
});
describe('Width regressions', () => {
test('should consider Fields Selector width when enabled', () => {
jest.mocked(getFieldSelectorWidth).mockClear();
setup({ showFieldSelector: true }, { labels: { key1: 'label1', key2: 'label2' } });
expect(screen.getByText('Log line')).toBeInTheDocument();
expect(screen.getByText('Fields')).toBeInTheDocument();
expect(getFieldSelectorWidth).toHaveBeenCalled();
});
test('should not consider Fields Selector width when disabled', () => {
jest.mocked(getFieldSelectorWidth).mockClear();
setup({ showFieldSelector: false }, { labels: { key1: 'label1', key2: 'label2' } });
expect(screen.getByText('Log line')).toBeInTheDocument();
expect(screen.getByText('Fields')).toBeInTheDocument();
expect(getFieldSelectorWidth).not.toHaveBeenCalled();
});
});
});
@@ -7,7 +7,7 @@ import { t } from '@grafana/i18n';
import { reportInteraction } from '@grafana/runtime';
import { getDragStyles, Icon, Tab, TabsBar, useStyles2 } from '@grafana/ui';
import { getSidebarWidth } from '../fieldSelector/FieldSelector';
import { getFieldSelectorWidth } from '../fieldSelector/FieldSelector';
import { getDetailsScrollPosition, saveDetailsScrollPosition, useLogDetailsContext } from './LogDetailsContext';
import { LogLineDetailsComponent } from './LogLineDetailsComponent';
@@ -22,12 +22,13 @@ export interface Props {
timeRange: TimeRange;
timeZone: string;
showControls: boolean;
showFieldSelector: boolean | undefined;
}
export type LogLineDetailsMode = 'inline' | 'sidebar';
export const LogLineDetails = memo(
({ containerElement, focusLogLine, logs, timeRange, timeZone, showControls }: Props) => {
({ containerElement, focusLogLine, logs, timeRange, timeZone, showControls, showFieldSelector }: Props) => {
const { noInteractions, logOptionsStorageKey } = useLogListContext();
const { detailsWidth, setDetailsWidth } = useLogDetailsContext();
const styles = useStyles2(getStyles, 'sidebar', showControls);
@@ -48,7 +49,10 @@ export const LogLineDetails = memo(
}
}, [noInteractions]);
const maxWidth = containerElement.clientWidth - getSidebarWidth(logOptionsStorageKey) - LOG_LIST_MIN_WIDTH;
const maxWidth =
containerElement.clientWidth -
(showFieldSelector ? getFieldSelectorWidth(logOptionsStorageKey) : 0) -
LOG_LIST_MIN_WIDTH;
return (
<Resizable
@@ -219,6 +219,7 @@ export const LogList = ({
logs={logs}
logOptionsStorageKey={logOptionsStorageKey}
showControls={showControls}
showFieldSelector={showFieldSelector}
>
<LogListSearchContextProvider>
<LogListComponent
@@ -458,6 +459,7 @@ const LogListComponent = ({
timeRange={timeRange}
timeZone={timeZone}
showControls={showControls}
showFieldSelector={showFieldSelector}
/>
)}
<div className={styles.logListWrapper} ref={wrapperRef}>
@@ -27,7 +27,7 @@ import { config, getDataSourceSrv } from '@grafana/runtime';
import { PopoverContent } from '@grafana/ui';
import { checkLogsError, checkLogsSampled, downloadLogs as download, DownloadFormat } from '../../utils';
import { getSidebarState } from '../fieldSelector/FieldSelector';
import { getFieldSelectorState } from '../fieldSelector/FieldSelector';
import { getDisplayedFieldsForLogs } from '../otel/formats';
import { getDefaultDetailsMode, getDetailsWidth } from './LogDetailsContext';
@@ -245,7 +245,7 @@ export const LogListContextProvider = ({
dedupStrategy,
fontSize,
forceEscape: logListState.forceEscape,
fieldSelectorOpen: getSidebarState(logOptionsStorageKey),
fieldSelectorOpen: getFieldSelectorState(logOptionsStorageKey),
showTime,
showUniqueLabels,
syntaxHighlighting,