From ddfca17d92e79ab3385e33227a0d24f32f7db7de Mon Sep 17 00:00:00 2001 From: L2D2Grafana Date: Fri, 12 Dec 2025 11:21:50 -0800 Subject: [PATCH] Logs: update tests --- .../Logs/utils/columnMigration.test.ts | 278 ++++++++++-------- 1 file changed, 157 insertions(+), 121 deletions(-) diff --git a/public/app/features/explore/Logs/utils/columnMigration.test.ts b/public/app/features/explore/Logs/utils/columnMigration.test.ts index e65d11a817d..4e30df05c69 100644 --- a/public/app/features/explore/Logs/utils/columnMigration.test.ts +++ b/public/app/features/explore/Logs/utils/columnMigration.test.ts @@ -6,6 +6,7 @@ import { mergeWithDefaults, hasLegacyColumns, extractColumnsValue, + extractDisplayedFields, migrateLegacyColumns, } from './columnMigration'; @@ -186,104 +187,128 @@ describe('columnMigration', () => { }); }); + describe('extractDisplayedFields', () => { + it('should extract displayedFields array', () => { + const state = { displayedFields: ['Time', 'level', 'host'] }; + expect(extractDisplayedFields(state)).toEqual(['Time', 'level', 'host']); + }); + + it('should return undefined when displayedFields not present', () => { + const state = { columns: ['Time'] }; + expect(extractDisplayedFields(state)).toBeUndefined(); + }); + + it('should extract empty displayedFields array', () => { + const state = { displayedFields: [] }; + expect(extractDisplayedFields(state)).toEqual([]); + }); + + it('should handle state with both columns and displayedFields', () => { + const state = { + columns: { '0': 'cluster', '1': 'Line' }, + displayedFields: ['service_name', 'component'], + }; + expect(extractDisplayedFields(state)).toEqual(['service_name', 'component']); + }); + }); + describe('migrateLegacyColumns', () => { const defaultDisplayedFields = ['Time', LOG_LINE_BODY_FIELD_NAME]; - it('should return null when logsState is null', () => { - expect(migrateLegacyColumns(null, defaultDisplayedFields)).toBeNull(); + describe('general behavior', () => { + it('should return null when logsState is null', () => { + expect(migrateLegacyColumns(null, defaultDisplayedFields, 'table')).toBeNull(); + }); + + it('should return null when logsState is undefined', () => { + expect(migrateLegacyColumns(undefined, defaultDisplayedFields, 'table')).toBeNull(); + }); + + it('should return null when no columns property exists', () => { + const logsState = { displayedFields: ['Time'] }; + expect(migrateLegacyColumns(logsState, defaultDisplayedFields, 'table')).toBeNull(); + }); + + it('should return null when visualisationType is not provided', () => { + const logsState = { columns: ['Time', 'level'] }; + expect(migrateLegacyColumns(logsState, defaultDisplayedFields)).toBeNull(); + }); + + it('should return null when visualisationType is unknown', () => { + const logsState = { columns: ['Time', 'level'] }; + expect(migrateLegacyColumns(logsState, defaultDisplayedFields, 'unknown')).toBeNull(); + }); }); - it('should return null when logsState is undefined', () => { - expect(migrateLegacyColumns(undefined, defaultDisplayedFields)).toBeNull(); - }); + describe('visualisationType: table', () => { + it('should return null when columns is empty array', () => { + const logsState = { columns: [] }; + expect(migrateLegacyColumns(logsState, defaultDisplayedFields, 'table')).toBeNull(); + }); - it('should return null when no columns property exists', () => { - const logsState = { displayedFields: ['Time'] }; - expect(migrateLegacyColumns(logsState, defaultDisplayedFields)).toBeNull(); - }); + it('should return null when columns is invalid', () => { + const logsState = { columns: 'invalid' }; + expect(migrateLegacyColumns(logsState, defaultDisplayedFields, 'table')).toBeNull(); + }); - it('should return null when columns is empty array', () => { - const logsState = { columns: [] }; - expect(migrateLegacyColumns(logsState, defaultDisplayedFields)).toBeNull(); - }); + it('should migrate array format columns', () => { + const logsState = { columns: ['Time', TABLE_LINE_FIELD_NAME, 'level'] }; + const result = migrateLegacyColumns(logsState, defaultDisplayedFields, 'table'); + expect(result).toEqual(['Time', LOG_LINE_BODY_FIELD_NAME, 'level']); + }); - it('should return null when columns is invalid', () => { - const logsState = { columns: 'invalid' }; - expect(migrateLegacyColumns(logsState, defaultDisplayedFields)).toBeNull(); - }); + it('should migrate object format columns', () => { + const logsState = { columns: { 0: 'Time', 1: TABLE_LINE_FIELD_NAME, 2: 'level' } }; + const result = migrateLegacyColumns(logsState, defaultDisplayedFields, 'table'); + expect(result).toEqual(['Time', LOG_LINE_BODY_FIELD_NAME, 'level']); + }); - it('should migrate array format columns', () => { - const logsState = { columns: ['Time', TABLE_LINE_FIELD_NAME, 'level'] }; - const result = migrateLegacyColumns(logsState, defaultDisplayedFields); - expect(result).toEqual(['Time', LOG_LINE_BODY_FIELD_NAME, 'level']); - }); + it('should return only mapped columns without merging with defaults', () => { + const logsState = { columns: ['level', 'host'] }; + const result = migrateLegacyColumns(logsState, defaultDisplayedFields, 'table'); + // Table visualization returns only the columns, not merged with defaults + expect(result).toEqual(['level', 'host']); + }); - it('should migrate object format columns', () => { - const logsState = { columns: { 0: 'Time', 1: TABLE_LINE_FIELD_NAME, 2: 'level' } }; - const result = migrateLegacyColumns(logsState, defaultDisplayedFields); - expect(result).toEqual(['Time', LOG_LINE_BODY_FIELD_NAME, 'level']); - }); + it('should map Line to body field name', () => { + const logsState = { columns: [TABLE_LINE_FIELD_NAME] }; + const result = migrateLegacyColumns(logsState, defaultDisplayedFields, 'table'); + expect(result).toEqual([LOG_LINE_BODY_FIELD_NAME]); + }); - it('should merge with defaults and avoid duplicates', () => { - const logsState = { columns: ['level', 'host'] }; - const result = migrateLegacyColumns(logsState, defaultDisplayedFields); - expect(result).toEqual(['Time', LOG_LINE_BODY_FIELD_NAME, 'level', 'host']); - }); + it('should map timestamp to Time', () => { + const logsState = { columns: ['timestamp', 'level'] }; + const result = migrateLegacyColumns(logsState, defaultDisplayedFields, 'table'); + expect(result).toEqual(['Time', 'level']); + }); - it('should handle columns that are already in defaults', () => { - const logsState = { columns: ['Time', 'level'] }; - const result = migrateLegacyColumns(logsState, defaultDisplayedFields); - expect(result).toEqual(['Time', LOG_LINE_BODY_FIELD_NAME, 'level']); - }); + it('should map body to LOG_LINE_BODY_FIELD_NAME', () => { + const logsState = { columns: ['body', 'level'] }; + const result = migrateLegacyColumns(logsState, defaultDisplayedFields, 'table'); + expect(result).toEqual([LOG_LINE_BODY_FIELD_NAME, 'level']); + }); - it('should map Line to body and merge correctly', () => { - const logsState = { columns: [TABLE_LINE_FIELD_NAME] }; - const result = migrateLegacyColumns(logsState, defaultDisplayedFields); - // Line maps to LOG_LINE_BODY_FIELD_NAME which is already in defaults - expect(result).toEqual(['Time', LOG_LINE_BODY_FIELD_NAME]); - }); + it('should ignore displayedFields and only use columns for table', () => { + const logsState = { + columns: ['level'], + displayedFields: ['existing', 'fields'], + }; + const result = migrateLegacyColumns(logsState, defaultDisplayedFields, 'table'); + // Should only return mapped columns, ignoring displayedFields + expect(result).toEqual(['level']); + }); - it('should handle empty defaults', () => { - const logsState = { columns: ['Time', TABLE_LINE_FIELD_NAME] }; - const result = migrateLegacyColumns(logsState, []); - expect(result).toEqual(['Time', LOG_LINE_BODY_FIELD_NAME]); - }); - - it('should preserve other logsState properties conceptually', () => { - // This test verifies the function only looks at columns - const logsState = { - columns: ['level'], - visualisationType: 'table', - displayedFields: ['existing'], - }; - const result = migrateLegacyColumns(logsState, defaultDisplayedFields); - expect(result).toEqual(['Time', LOG_LINE_BODY_FIELD_NAME, 'level']); - }); - - it('should handle real URL format with full logsState structure', () => { - // Real format from URL: columns%22:%7B%220%22:%22cluster%22,%221%22:%22Line%22,%222%22:%22Time%22%7D - // Full structure: {"columns":{"0":"cluster","1":"Line","2":"Time"},"visualisationType":"table","labelFieldName":"labels","refId":"A"} - const logsState = { - columns: { '0': 'cluster', '1': 'Line', '2': 'Time' }, - visualisationType: 'table', - labelFieldName: 'labels', - refId: 'A', - }; - const result = migrateLegacyColumns(logsState, defaultDisplayedFields); - // cluster is new, Line maps to body (already in defaults), Time is already in defaults - expect(result).toEqual(['Time', LOG_LINE_BODY_FIELD_NAME, 'cluster']); - }); - - describe('real URL format from ops.grafana-ops.net', () => { - // URL: https://ops.grafana-ops.net/explore?schemaVersion=1&panes=%7B%2259n%22:... - // Decoded panelsState.logs: - // { - // "sortOrder": "Ascending", - // "columns": {"0": "cluster", "1": "Line", "2": "Time"}, - // "visualisationType": "table", - // "labelFieldName": "labels", - // "refId": "A" - // } + it('should handle real URL format with full logsState structure', () => { + const logsState = { + columns: { '0': 'cluster', '1': 'Line', '2': 'Time' }, + visualisationType: 'table', + labelFieldName: 'labels', + refId: 'A', + }; + const result = migrateLegacyColumns(logsState, defaultDisplayedFields, 'table'); + // Returns mapped columns in order (Line -> LOG_LINE_BODY_FIELD_NAME) + expect(result).toEqual(['cluster', LOG_LINE_BODY_FIELD_NAME, 'Time']); + }); it('should migrate columns from real Grafana Explore URL', () => { const logsState = { @@ -293,51 +318,62 @@ describe('columnMigration', () => { labelFieldName: 'labels', refId: 'A', }; - const result = migrateLegacyColumns(logsState, defaultDisplayedFields); + const result = migrateLegacyColumns(logsState, defaultDisplayedFields, 'table'); expect(result).toContain('cluster'); expect(result).toContain(LOG_LINE_BODY_FIELD_NAME); expect(result).toContain('Time'); + expect(result).not.toContain('Line'); // Line should be mapped }); - it('should map Line to body field name', () => { + it('should map legacy field names correctly', () => { + const logsState = { + columns: { '0': 'timestamp', '1': 'body', '2': 'env', '3': 'namespace' }, + }; + const result = migrateLegacyColumns(logsState, defaultDisplayedFields, 'table'); + expect(result).toEqual(['Time', LOG_LINE_BODY_FIELD_NAME, 'env', 'namespace']); + }); + }); + + describe('visualisationType: logs', () => { + it('should return null when no columns property exists (required for migration)', () => { + const logsState = { displayedFields: ['Time', 'level'] }; + // logs visualization requires legacy columns to exist for migration to run + expect(migrateLegacyColumns(logsState, defaultDisplayedFields, 'logs')).toBeNull(); + }); + + it('should return displayedFields directly when columns exist', () => { + const logsState = { + columns: { '0': 'old', '1': 'columns' }, // Legacy columns must exist + displayedFields: ['Time', 'level', 'host'], + }; + const result = migrateLegacyColumns(logsState, defaultDisplayedFields, 'logs'); + expect(result).toEqual(['Time', 'level', 'host']); + }); + + it('should return null when displayedFields is empty', () => { + const logsState = { + columns: { '0': 'old' }, + displayedFields: [], + }; + expect(migrateLegacyColumns(logsState, defaultDisplayedFields, 'logs')).toBeNull(); + }); + + it('should return null when displayedFields is not an array', () => { + const logsState = { + columns: { '0': 'old' }, + displayedFields: 'not-an-array', + }; + expect(migrateLegacyColumns(logsState, defaultDisplayedFields, 'logs')).toBeNull(); + }); + + it('should ignore columns and use displayedFields for logs visualization', () => { const logsState = { - sortOrder: 'Ascending', columns: { '0': 'cluster', '1': 'Line', '2': 'Time' }, - visualisationType: 'table', - labelFieldName: 'labels', - refId: 'A', + displayedFields: ['service_name', 'component'], }; - const result = migrateLegacyColumns(logsState, defaultDisplayedFields); - // Line should be mapped to LOG_LINE_BODY_FIELD_NAME, not remain as 'Line' - expect(result).not.toContain('Line'); - expect(result).toContain(LOG_LINE_BODY_FIELD_NAME); - }); - - it('should place defaults first, then additional columns', () => { - const logsState = { - sortOrder: 'Ascending', - columns: { '0': 'cluster', '1': 'Line', '2': 'Time' }, - visualisationType: 'table', - labelFieldName: 'labels', - refId: 'A', - }; - const result = migrateLegacyColumns(logsState, defaultDisplayedFields); - // Default fields (Time, body) should come first - expect(result![0]).toBe('Time'); - expect(result![1]).toBe(LOG_LINE_BODY_FIELD_NAME); - // Then additional fields from columns (cluster) - expect(result![2]).toBe('cluster'); - }); - - it('should not duplicate fields that exist in both defaults and columns', () => { - const logsState = { - columns: { '0': 'Time', '1': 'Line', '2': 'cluster' }, - visualisationType: 'table', - }; - const result = migrateLegacyColumns(logsState, defaultDisplayedFields); - // Should not have duplicate Time or body entries - expect(result!.filter((f) => f === 'Time').length).toBe(1); - expect(result!.filter((f) => f === LOG_LINE_BODY_FIELD_NAME).length).toBe(1); + const result = migrateLegacyColumns(logsState, defaultDisplayedFields, 'logs'); + // Should return displayedFields, ignoring columns + expect(result).toEqual(['service_name', 'component']); }); }); });