From b40d0e6ff4c3b02a9c8be9b75282f2c2e2b1fe35 Mon Sep 17 00:00:00 2001 From: Kristina Demeshchik Date: Fri, 12 Dec 2025 10:36:31 -0500 Subject: [PATCH 1/2] Dashboards: Fix accessible color palettes not being saved in v2 schema (#115244) * Fix palette color v2 conversion * v2->v1 conversion --- .../serialization/transformToV2TypesUtils.ts | 10 ++++++++++ .../app/features/dashboard/api/ResponseTransformers.ts | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts b/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts index fe1d85e6525..8b17ab8ff89 100644 --- a/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts +++ b/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts @@ -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: diff --git a/public/app/features/dashboard/api/ResponseTransformers.ts b/public/app/features/dashboard/api/ResponseTransformers.ts index b6fcba03de3..d2640991b1e 100644 --- a/public/app/features/dashboard/api/ResponseTransformers.ts +++ b/public/app/features/dashboard/api/ResponseTransformers.ts @@ -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': From 7114b9cd3bafa4d108081c8d95ac0ed6e31928f6 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Fri, 12 Dec 2025 16:56:23 +0100 Subject: [PATCH 2/2] Log Line Details: Fix width calculation in dashboards (#115248) * FieldSelector: rename functions to be more explicit * LogDetailsContext: calculate width based on field selector visibility * LogLineDetails: Fix sidebar max width calculation * Update functions usage * Add regression and fix context calculation --- .../features/explore/Logs/LogsTableWrap.tsx | 4 +-- .../fieldSelector/FieldSelector.tsx | 10 +++--- .../components/panel/LogDetailsContext.tsx | 36 +++++++++++++------ .../components/panel/LogLineDetails.test.tsx | 26 ++++++++++++++ .../logs/components/panel/LogLineDetails.tsx | 10 ++++-- .../logs/components/panel/LogList.tsx | 2 ++ .../logs/components/panel/LogListContext.tsx | 4 +-- 7 files changed, 70 insertions(+), 22 deletions(-) diff --git a/public/app/features/explore/Logs/LogsTableWrap.tsx b/public/app/features/explore/Logs/LogsTableWrap.tsx index ff8cceaadcf..cde546d92ba 100644 --- a/public/app/features/explore/Logs/LogsTableWrap.tsx +++ b/public/app/features/explore/Logs/LogsTableWrap.tsx @@ -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); diff --git a/public/app/features/logs/components/fieldSelector/FieldSelector.tsx b/public/app/features/logs/components/fieldSelector/FieldSelector.tsx index 44b1ff0f85c..d68cf5340c9 100644 --- a/public/app/features/logs/components/fieldSelector/FieldSelector.tsx +++ b/public/app/features/logs/components/fieldSelector/FieldSelector.tsx @@ -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; } diff --git a/public/app/features/logs/components/panel/LogDetailsContext.tsx b/public/app/features/logs/components/panel/LogDetailsContext.tsx index de0c044911e..d3526e2bcb4 100644 --- a/public/app/features/logs/components/panel/LogDetailsContext.tsx +++ b/public/app/features/logs/components/panel/LogDetailsContext.tsx @@ -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([]); const [currentLog, setCurrentLog] = useState(undefined); const [detailsWidth, setDetailsWidthState] = useState( - getDetailsWidth(containerElement, logOptionsStorageKey, undefined, detailsModeProp, showControls) + getDetailsWidth(containerElement, logOptionsStorageKey, undefined, detailsModeProp, showControls, showFieldSelector) ); const [detailsMode, setDetailsMode] = useState( 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); } diff --git a/public/app/features/logs/components/panel/LogLineDetails.test.tsx b/public/app/features/logs/components/panel/LogLineDetails.test.tsx index dafc055ce0d..cee12e10d8b 100644 --- a/public/app/features/logs/components/panel/LogLineDetails.test.tsx +++ b/public/app/features/logs/components/panel/LogLineDetails.test.tsx @@ -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(); + }); + }); }); diff --git a/public/app/features/logs/components/panel/LogLineDetails.tsx b/public/app/features/logs/components/panel/LogLineDetails.tsx index 19639e61f8c..bc5a961cbd7 100644 --- a/public/app/features/logs/components/panel/LogLineDetails.tsx +++ b/public/app/features/logs/components/panel/LogLineDetails.tsx @@ -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 ( )}
diff --git a/public/app/features/logs/components/panel/LogListContext.tsx b/public/app/features/logs/components/panel/LogListContext.tsx index a917b0eac56..139f40ea98e 100644 --- a/public/app/features/logs/components/panel/LogListContext.tsx +++ b/public/app/features/logs/components/panel/LogListContext.tsx @@ -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,