From 2aedbdb76ff606febee27d235118bca28bd4a6ce Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Mon, 12 Jan 2026 13:37:17 +0100 Subject: [PATCH 01/66] processing: support duplicated keys when parsing json logs (#116116) * processing: support duplicated keys when parsing json logs * Add regression test * prettier --- .../logs/components/panel/processing.test.ts | 16 ++++++++++++++++ .../features/logs/components/panel/processing.ts | 4 +++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/public/app/features/logs/components/panel/processing.test.ts b/public/app/features/logs/components/panel/processing.test.ts index 5998ff36d21..748c53b1df4 100644 --- a/public/app/features/logs/components/panel/processing.test.ts +++ b/public/app/features/logs/components/panel/processing.test.ts @@ -190,6 +190,22 @@ describe('preProcessLogs', () => { expect(logListModel.body).not.toBe(entry); }); + test('Prettifies JSON with duplicate keys', () => { + const entry = '{"key": "value", "key": "otherValue"}'; + const logListModel = createLogLine( + { entry }, + { + escape: false, + order: LogsSortOrder.Descending, + timeZone: 'browser', + wrapLogMessage: true, // wrapped + prettifyJSON: true, + } + ); + expect(logListModel.entry).toBe(entry); + expect(logListModel.body).not.toBe(entry); + }); + test('Prettifies and escapes wrapped JSON', () => { const entry = '{"key": "value", "otherKey": "other\\nValue"}'; const logListModel = createLogLine( diff --git a/public/app/features/logs/components/panel/processing.ts b/public/app/features/logs/components/panel/processing.ts index 9132c2b7e6e..814481e87f2 100644 --- a/public/app/features/logs/components/panel/processing.ts +++ b/public/app/features/logs/components/panel/processing.ts @@ -135,7 +135,9 @@ export class LogListModel implements LogRowModel { get body(): string { if (this._body === undefined) { try { - const parsed = parse(this.raw); + const parsed = parse(this.raw, undefined, { + onDuplicateKey: ({ newValue }) => newValue, + }); if (typeof parsed === 'object' && parsed !== null && !(parsed instanceof LosslessNumber)) { this._json = true; } From b91ca14f48894cedd18df87c9085a7cfd4638cc3 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:38:18 +0100 Subject: [PATCH 02/66] Icons: Add brain icon (#116023) * Icons: Add brain icon * lint * Add brain to cached icons --- packages/grafana-data/src/types/icon.ts | 1 + public/app/core/icons/cached.json | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/grafana-data/src/types/icon.ts b/packages/grafana-data/src/types/icon.ts index 34e672b66f5..3dc7215a2cf 100644 --- a/packages/grafana-data/src/types/icon.ts +++ b/packages/grafana-data/src/types/icon.ts @@ -52,6 +52,7 @@ export const availableIconsIndex = { bookmark: true, 'book-open': true, 'brackets-curly': true, + brain: true, 'browser-alt': true, bug: true, building: true, diff --git a/public/app/core/icons/cached.json b/public/app/core/icons/cached.json index 6e35e64dd0c..9987d867738 100644 --- a/public/app/core/icons/cached.json +++ b/public/app/core/icons/cached.json @@ -29,6 +29,7 @@ "unicons/bookmark", "unicons/book-open", "unicons/brackets-curly", + "unicons/brain", "unicons/bug", "unicons/building", "unicons/calculator-alt", From bec4d225b378c81ac95b4d00ca822da0f2eb3beb Mon Sep 17 00:00:00 2001 From: Kristina Demeshchik Date: Mon, 12 Jan 2026 09:29:16 -0500 Subject: [PATCH 03/66] FieldConfig: Fix multiple value mappings in field overrides being overwritten (#116027) * multiple value mappingg overrides * add comment for clarity * remove extra check --- .../src/field/fieldOverrides.test.ts | 40 +++++++++++++++++++ .../grafana-data/src/field/fieldOverrides.ts | 11 ++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/grafana-data/src/field/fieldOverrides.test.ts b/packages/grafana-data/src/field/fieldOverrides.test.ts index 767da439543..b0aa271be3f 100644 --- a/packages/grafana-data/src/field/fieldOverrides.test.ts +++ b/packages/grafana-data/src/field/fieldOverrides.test.ts @@ -9,6 +9,7 @@ import { FieldColorModeId } from '../types/fieldColor'; import { FieldConfigPropertyItem, FieldConfigSource } from '../types/fieldOverrides'; import { InterpolateFunction } from '../types/panel'; import { ThresholdsMode } from '../types/thresholds'; +import { MappingType } from '../types/valueMapping'; import { Registry } from '../utils/Registry'; import { locationUtil } from '../utils/location'; import { mockStandardProperties } from '../utils/tests/mockStandardProperties'; @@ -999,6 +1000,45 @@ describe('setDynamicConfigValue', () => { expect(config.custom.property3).toEqual({}); expect(config.displayName).toBeUndefined(); }); + + it('works correctly with multiple value mappings in the same override', () => { + const config: FieldConfig = { + mappings: [{ type: MappingType.ValueToText, options: { existing: { text: 'existing' } } }], + }; + + setDynamicConfigValue( + config, + { + id: 'mappings', + value: [{ type: MappingType.ValueToText, options: { first: { text: 'first' } } }], + }, + { + fieldConfigRegistry: customFieldRegistry, + data: [], + field: { type: FieldType.number } as Field, + dataFrameIndex: 0, + } + ); + + setDynamicConfigValue( + config, + { + id: 'mappings', + value: [{ type: MappingType.ValueToText, options: { second: { text: 'second' } } }], + }, + { + fieldConfigRegistry: customFieldRegistry, + data: [], + field: { type: FieldType.number } as Field, + dataFrameIndex: 0, + } + ); + + expect(config.mappings).toHaveLength(3); + expect(config.mappings![0]).toEqual({ type: MappingType.ValueToText, options: { existing: { text: 'existing' } } }); + expect(config.mappings![1]).toEqual({ type: MappingType.ValueToText, options: { first: { text: 'first' } } }); + expect(config.mappings![2]).toEqual({ type: MappingType.ValueToText, options: { second: { text: 'second' } } }); + }); }); describe('getLinksSupplier', () => { diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts index 8b345036c64..bf56f811106 100644 --- a/packages/grafana-data/src/field/fieldOverrides.ts +++ b/packages/grafana-data/src/field/fieldOverrides.ts @@ -341,7 +341,7 @@ export function setDynamicConfigValue(config: FieldConfig, value: DynamicConfigV return; } - const val = item.process(value.value, context, item.settings); + let val = item.process(value.value, context, item.settings); const remove = val === undefined || val === null; @@ -352,6 +352,15 @@ export function setDynamicConfigValue(config: FieldConfig, value: DynamicConfigV unset(config, item.path); } } else { + // Merge arrays (e.g. mappings) when multiple overrides target the same field + if (Array.isArray(val)) { + const existingValue = item.isCustom ? get(config.custom, item.path) : get(config, item.path); + + if (Array.isArray(existingValue)) { + val = [...existingValue, ...val]; + } + } + if (item.isCustom) { if (!config.custom) { config.custom = {}; From 77c500dc01385b42d940593e7c2913af8cdb9b78 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Mon, 12 Jan 2026 15:46:47 +0100 Subject: [PATCH 04/66] logsExploreTableDefaultVisualization: remove feature flag (#116127) --- packages/grafana-data/src/types/featureToggles.gen.ts | 4 ---- pkg/services/featuremgmt/registry.go | 7 ------- pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.json | 3 ++- public/app/features/explore/Logs/Logs.tsx | 4 ---- 5 files changed, 2 insertions(+), 17 deletions(-) diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 0bd17e2af72..31359af52cf 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -527,10 +527,6 @@ export interface FeatureToggles { */ dashboardTemplates?: boolean; /** - * Sets the logs table as default visualisation in logs explore - */ - logsExploreTableDefaultVisualization?: boolean; - /** * Enables the new alert list view design */ alertingListViewV2?: boolean; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 6f2274f22e7..f6d9e2da560 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -872,13 +872,6 @@ var ( Owner: grafanaSharingSquad, FrontendOnly: false, }, - { - Name: "logsExploreTableDefaultVisualization", - Description: "Sets the logs table as default visualisation in logs explore", - Stage: FeatureStageExperimental, - Owner: grafanaObservabilityLogsSquad, - FrontendOnly: true, - }, { Name: "alertingListViewV2", Description: "Enables the new alert list view design", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index c4d2c026d9a..fdb265c7980 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -120,7 +120,6 @@ queryLibrary,preview,@grafana/sharing-squad,false,false,false dashboardLibrary,experimental,@grafana/sharing-squad,false,false,false suggestedDashboards,experimental,@grafana/sharing-squad,false,false,false dashboardTemplates,preview,@grafana/sharing-squad,false,false,false -logsExploreTableDefaultVisualization,experimental,@grafana/observability-logs,false,false,true alertingListViewV2,privatePreview,@grafana/alerting-squad,false,false,true alertingSavedSearches,experimental,@grafana/alerting-squad,false,false,true alertingDisableSendAlertsExternal,experimental,@grafana/alerting-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 64d6ff7b5dd..5747b9bdd20 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2246,7 +2246,8 @@ "metadata": { "name": "logsExploreTableDefaultVisualization", "resourceVersion": "1764664939750", - "creationTimestamp": "2024-05-02T15:28:15Z" + "creationTimestamp": "2024-05-02T15:28:15Z", + "deletionTimestamp": "2026-01-12T14:11:46Z" }, "spec": { "description": "Sets the logs table as default visualisation in logs explore", diff --git a/public/app/features/explore/Logs/Logs.tsx b/public/app/features/explore/Logs/Logs.tsx index 46930f67552..6d3c0a90739 100644 --- a/public/app/features/explore/Logs/Logs.tsx +++ b/public/app/features/explore/Logs/Logs.tsx @@ -149,9 +149,6 @@ const getDefaultVisualisationType = (): LogsVisualisationType => { if (visualisationType === 'logs') { return 'logs'; } - if (config.featureToggles.logsExploreTableDefaultVisualization) { - return 'table'; - } return 'logs'; }; @@ -447,7 +444,6 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => { reportInteraction('grafana_explore_logs_visualisation_changed', { newVisualizationType: visualisation, datasourceType: props.datasourceType ?? 'unknown', - defaultVisualisationType: config.featureToggles.logsExploreTableDefaultVisualization ? 'table' : 'logs', }); }, [panelState?.logs, props.datasourceType, updatePanelState] From 6dc604c2ea08de488f45ab6639375527e19fb5bb Mon Sep 17 00:00:00 2001 From: Yunwen Zheng Date: Mon, 12 Jan 2026 09:56:15 -0500 Subject: [PATCH 05/66] RecentlyViewedDashboards: Add instrumentations (#116036) RecentlyViewedDashboards: Add instrumentation --- .../components/RecentlyViewedDashboards.tsx | 14 ++++++++++-- .../plugins/panel/dashlist/DashListItem.tsx | 22 +++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx b/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx index effa0c8009b..f0c2d4d3a13 100644 --- a/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx +++ b/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx @@ -4,6 +4,7 @@ import { useAsyncRetry } from 'react-use'; import { GrafanaTheme2, store } from '@grafana/data'; import { t, Trans } from '@grafana/i18n'; +import { reportInteraction } from '@grafana/runtime'; import { evaluateBooleanFlag } from '@grafana/runtime/internal'; import { Button, CollapsableSection, Spinner, Stack, Text, useStyles2, Grid } from '@grafana/ui'; import { contextSrv } from 'app/core/services/context_srv'; @@ -35,10 +36,18 @@ export function RecentlyViewedDashboards() { const { foldersByUid } = useDashboardLocationInfo(recentDashboards.length > 0); const handleClearHistory = () => { + reportInteraction('grafana_recently_viewed_dashboards_clear_history'); store.set(recentDashboardsKey, JSON.stringify([])); retry(); }; + const handleSectionToggle = () => { + reportInteraction('grafana_recently_viewed_dashboards_toggle_section', { + expanded: !isOpen, + }); + setIsOpen(!isOpen); + }; + if (!evaluateBooleanFlag('recentlyViewedDashboards', false) || recentDashboards.length === 0) { return null; } @@ -48,7 +57,7 @@ export function RecentlyViewedDashboards() { headerDataTestId="browseDashboardsRecentlyViewedTitle" label={ - setIsOpen(!isOpen)}> + Recently viewed