diff --git a/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx b/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx index ca4a65e6fb10f..8adcf3ef79122 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx @@ -64,7 +64,7 @@ export function HeatmapDimensionEditor( { setIsPaletteOpen(!isPaletteOpen); diff --git a/x-pack/plugins/lens/public/heatmap_visualization/utils.ts b/x-pack/plugins/lens/public/heatmap_visualization/utils.ts index a21c07b874bff..3f860be646f35 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/utils.ts +++ b/x-pack/plugins/lens/public/heatmap_visualization/utils.ts @@ -14,9 +14,12 @@ import type { HeatmapVisualizationState } from './types'; export function getSafePaletteParams( paletteService: PaletteRegistry, currentData: Datatable | undefined, - accessor: string, + accessor: string | undefined, activePalette?: HeatmapVisualizationState['palette'] ) { + if (currentData == null || accessor == null) { + return { displayStops: [], activePalette: {} as HeatmapVisualizationState['palette'] }; + } const finalActivePalette: HeatmapVisualizationState['palette'] = activePalette ?? { type: 'palette', name: DEFAULT_PALETTE_NAME, diff --git a/x-pack/plugins/lens/public/heatmap_visualization/visualization.test.ts b/x-pack/plugins/lens/public/heatmap_visualization/visualization.test.ts index 5e7ee1b8b097b..91b90e11470fc 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/heatmap_visualization/visualization.test.ts @@ -98,7 +98,12 @@ describe('heatmap', () => { }; }); - test('resolves configuration from complete state', () => { + afterEach(() => { + // some tests manipulate it, so restore a pristine version + frame = createMockFramePublicAPI(); + }); + + test('resolves configuration from complete state and available data', () => { const state: HeatmapVisualizationState = { ...exampleState(), layerId: 'first', @@ -107,6 +112,8 @@ describe('heatmap', () => { valueAccessor: 'v-accessor', }; + frame.activeData = { first: { type: 'datatable', columns: [], rows: [] } }; + expect( getHeatmapVisualization({ paletteService, @@ -204,6 +211,63 @@ describe('heatmap', () => { ], }); }); + + test("resolves configuration when there's no access to active data in frame", () => { + const state: HeatmapVisualizationState = { + ...exampleState(), + layerId: 'first', + xAccessor: 'x-accessor', + yAccessor: 'y-accessor', + valueAccessor: 'v-accessor', + }; + + frame.activeData = undefined; + + expect( + getHeatmapVisualization({ + paletteService, + }).getConfiguration({ state, frame, layerId: 'first' }) + ).toEqual({ + groups: [ + { + layerId: 'first', + groupId: GROUP_ID.X, + groupLabel: 'Horizontal axis', + accessors: [{ columnId: 'x-accessor' }], + filterOperations: filterOperationsAxis, + supportsMoreColumns: false, + required: true, + dataTestSubj: 'lnsHeatmap_xDimensionPanel', + }, + { + layerId: 'first', + groupId: GROUP_ID.Y, + groupLabel: 'Vertical axis', + accessors: [{ columnId: 'y-accessor' }], + filterOperations: filterOperationsAxis, + supportsMoreColumns: false, + required: false, + dataTestSubj: 'lnsHeatmap_yDimensionPanel', + }, + { + layerId: 'first', + groupId: GROUP_ID.CELL, + groupLabel: 'Cell value', + accessors: [ + { + columnId: 'v-accessor', + triggerIcon: 'none', + }, + ], + filterOperations: isCellValueSupported, + supportsMoreColumns: false, + required: true, + dataTestSubj: 'lnsHeatmap_cellPanel', + enableDimensionEditor: true, + }, + ], + }); + }); }); describe('#setDimension', () => { diff --git a/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx b/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx index 62e3138f397da..674af79db6c90 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx @@ -158,16 +158,12 @@ export const getHeatmapVisualization = ({ return { groups: [] }; } - const { displayStops, activePalette } = state.valueAccessor - ? getSafePaletteParams( - paletteService, - frame.activeData?.[state.layerId], - state.valueAccessor, - state?.palette && state.palette.accessor === state.valueAccessor - ? state.palette - : undefined - ) - : { displayStops: [], activePalette: {} as HeatmapVisualizationState['palette'] }; + const { displayStops, activePalette } = getSafePaletteParams( + paletteService, + frame.activeData?.[state.layerId], + state.valueAccessor, + state?.palette && state.palette.accessor === state.valueAccessor ? state.palette : undefined + ); return { groups: [ @@ -199,11 +195,21 @@ export const getHeatmapVisualization = ({ }), accessors: state.valueAccessor ? [ - { - columnId: state.valueAccessor, - triggerIcon: 'colorBy', - palette: getStopsForFixedMode(displayStops, activePalette?.params?.colorStops), - }, + // When data is not available and the range type is numeric, return a placeholder while refreshing + displayStops.length && + (frame.activeData || activePalette?.params?.rangeType !== 'number') + ? { + columnId: state.valueAccessor, + triggerIcon: 'colorBy', + palette: getStopsForFixedMode( + displayStops, + activePalette?.params?.colorStops + ), + } + : { + columnId: state.valueAccessor, + triggerIcon: 'none', + }, ] : [], filterOperations: isCellValueSupported, diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.tsx index 019e83fb0aa59..0493a212f46de 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.tsx +++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.tsx @@ -75,11 +75,14 @@ export function CustomizablePalette({ showContinuity = true, }: { palettes: PaletteRegistry; - activePalette: PaletteOutput; + activePalette?: PaletteOutput; setPalette: (palette: PaletteOutput) => void; - dataBounds: { min: number; max: number }; + dataBounds?: { min: number; max: number }; showContinuity?: boolean; }) { + if (!dataBounds || !activePalette) { + return null; + } const isCurrentPaletteCustom = activePalette.params?.name === CUSTOM_PALETTE; const colorStopsToShow = roundStopValues( diff --git a/x-pack/test/functional/apps/lens/add_to_dashboard.ts b/x-pack/test/functional/apps/lens/add_to_dashboard.ts index 6a04415fab26e..55d8ff9cf7621 100644 --- a/x-pack/test/functional/apps/lens/add_to_dashboard.ts +++ b/x-pack/test/functional/apps/lens/add_to_dashboard.ts @@ -236,6 +236,60 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(panelCount).to.eql(2); }); + // issue #111104 + it('should add a Lens heatmap to the dashboard', async () => { + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.clickNewDashboard(); + + await PageObjects.dashboard.saveDashboard('My Wonderful Heatmap dashboard'); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await listingTable.searchAndExpectItemsCount( + 'dashboard', + 'My Wonderful Heatmap dashboard', + 1 + ); + + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'ip', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + + await PageObjects.lens.waitForVisualization(); + + await PageObjects.lens.switchToVisualization('heatmap', 'heatmap'); + + await PageObjects.lens.waitForVisualization(); + await PageObjects.lens.openDimensionEditor('lnsHeatmap_cellPanel > lns-dimensionTrigger'); + await PageObjects.lens.openPalettePanel('lnsHeatmap'); + await testSubjects.click('lnsPalettePanel_dynamicColoring_rangeType_groups_number'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await PageObjects.lens.save( + 'New Lens Heatmap', + false, + false, + true, + 'existing', + 'My Wonderful Heatmap dashboard' + ); + + await PageObjects.dashboard.waitForRenderComplete(); + + const panelCount = await PageObjects.dashboard.getPanelCount(); + expect(panelCount).to.eql(1); + }); + describe('Capabilities', function capabilitiesTests() { describe('dashboard no-access privileges', () => { before(async () => {