[release-11.6.3] Geomap: Require layer to have data for legend (#106228)

* Geomap: Require layer to have data for legend (#105580)

(cherry picked from commit 16a6d61ca7)

* Remove test for non-existent function

* Sync up go work sum

* Remove unused import
pull/106673/head
Drew Slobodnjak 2 weeks ago committed by GitHub
parent 2e72ee654e
commit 3aef8f8331
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 10
      public/app/plugins/panel/geomap/GeomapPanel.tsx
  2. 120
      public/app/plugins/panel/geomap/utils/utils.test.ts
  3. 48
      public/app/plugins/panel/geomap/utils/utils.ts

@ -31,7 +31,7 @@ import { getActions } from './utils/actions';
import { getLayersExtent } from './utils/getLayersExtent';
import { applyLayerFilter, initLayer } from './utils/layers';
import { pointerClickListener, pointerMoveListener, setTooltipListeners } from './utils/tooltip';
import { updateMap, getNewOpenLayersMap, notifyPanelEditor } from './utils/utils';
import { updateMap, getNewOpenLayersMap, notifyPanelEditor, hasLayerData } from './utils/utils';
import { centerPointRegistry, MapCenterID } from './view';
// Allows multiple panels to share the same view instance
@ -182,6 +182,9 @@ export class GeomapPanel extends Component<Props, State> {
this.map.setView(view);
}
}
// Update legends when data changes
this.setState({ legends: this.getLegends() });
}
initMapRef = async (div: HTMLDivElement) => {
@ -366,7 +369,10 @@ export class GeomapPanel extends Component<Props, State> {
const legends: ReactNode[] = [];
for (const state of this.layers) {
if (state.handler.legend) {
legends.push(<div key={state.options.name}>{state.handler.legend}</div>);
const hasData = hasLayerData(state.layer);
if (hasData) {
legends.push(<div key={state.options.name}>{state.handler.legend}</div>);
}
}
}

@ -0,0 +1,120 @@
import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import LayerGroup from 'ol/layer/Group';
import TileLayer from 'ol/layer/Tile';
import VectorLayer from 'ol/layer/Vector';
import WebGLPointsLayer from 'ol/layer/WebGLPoints';
import TileSource from 'ol/source/Tile';
import VectorSource from 'ol/source/Vector';
// Mock the config module to avoid undefined panels error
jest.mock('@grafana/runtime', () => ({
getTemplateSrv: jest.fn(),
}));
// Mock the dimensions module since it's imported by utils.ts
jest.mock('app/features/dimensions', () => ({
getColorDimension: jest.fn(),
getScalarDimension: jest.fn(),
getScaledDimension: jest.fn(),
getTextDimension: jest.fn(),
}));
// Mock the grafana datasource since it's imported by utils.ts
jest.mock('app/plugins/datasource/grafana/datasource', () => ({
getGrafanaDatasource: jest.fn(),
}));
import { hasLayerData } from './utils';
// Test fixtures
const createTestFeature = () => new Feature(new Point([0, 0]));
const createTestVectorSource = (hasFeature = false): VectorSource<Point> => {
const source = new VectorSource<Point>();
if (hasFeature) {
source.addFeature(createTestFeature());
}
return source;
};
const createTestWebGLStyle = () => ({
symbol: {
symbolType: 'circle',
size: 8,
color: '#000000',
opacity: 1,
},
});
describe('hasLayerData', () => {
it('should return false for empty vector layer', () => {
const layer = new VectorLayer({
source: createTestVectorSource(),
});
expect(hasLayerData(layer)).toBe(false);
});
it('should return true for vector layer with features', () => {
const layer = new VectorLayer({
source: createTestVectorSource(true),
});
expect(hasLayerData(layer)).toBe(true);
});
it('should return true for layer group with data', () => {
const vectorLayer = new VectorLayer({
source: createTestVectorSource(true),
});
const group = new LayerGroup({
layers: [vectorLayer],
});
expect(hasLayerData(group)).toBe(true);
});
it('should return false for empty layer group', () => {
const group = new LayerGroup({
layers: [],
});
expect(hasLayerData(group)).toBe(false);
});
it('should return true for tile layer with source', () => {
const layer = new TileLayer({
source: new TileSource({}),
});
expect(hasLayerData(layer)).toBe(true);
});
it('should return false for tile layer without source', () => {
const layer = new TileLayer({});
expect(hasLayerData(layer)).toBe(false);
});
it('should return true for WebGLPointsLayer with features', () => {
const layer = new WebGLPointsLayer({
source: createTestVectorSource(true),
style: createTestWebGLStyle(),
});
expect(hasLayerData(layer)).toBe(true);
});
it('should return false for empty WebGLPointsLayer', () => {
const layer = new WebGLPointsLayer({
source: createTestVectorSource(),
style: createTestWebGLStyle(),
});
expect(hasLayerData(layer)).toBe(false);
});
it('should return true for layer group with WebGLPointsLayer containing data', () => {
const webglLayer = new WebGLPointsLayer({
source: createTestVectorSource(true),
style: createTestWebGLStyle(),
});
const group = new LayerGroup({
layers: [webglLayer],
});
expect(hasLayerData(group)).toBe(true);
});
});

@ -1,5 +1,17 @@
import { Map as OpenLayersMap } from 'ol';
import Geometry from 'ol/geom/Geometry';
import Point from 'ol/geom/Point';
import { defaults as interactionDefaults } from 'ol/interaction';
import BaseLayer from 'ol/layer/Base';
import LayerGroup from 'ol/layer/Group';
import ImageLayer from 'ol/layer/Image';
import TileLayer from 'ol/layer/Tile';
import VectorLayer from 'ol/layer/Vector';
import VectorImage from 'ol/layer/VectorImage';
import WebGLPointsLayer from 'ol/layer/WebGLPoints';
import ImageSource from 'ol/source/Image';
import TileSource from 'ol/source/Tile';
import VectorSource from 'ol/source/Vector';
import { SelectableValue } from '@grafana/data';
import { DataFrame, GrafanaTheme2 } from '@grafana/data/src';
@ -144,3 +156,39 @@ export const isUrl = (url: string) => {
return false;
}
};
/**
* Checks if a layer has data to display
* @param layer The OpenLayers layer to check
* @returns boolean indicating if the layer has data
*/
export function hasLayerData(
layer:
| LayerGroup
| VectorLayer<VectorSource<Geometry>>
| VectorImage<VectorSource<Geometry>>
| WebGLPointsLayer<VectorSource<Point>>
| TileLayer<TileSource>
| ImageLayer<ImageSource>
| BaseLayer
): boolean {
if (layer instanceof LayerGroup) {
return layer
.getLayers()
.getArray()
.some((subLayer) => hasLayerData(subLayer));
}
if (layer instanceof VectorLayer || layer instanceof VectorImage) {
const source = layer.getSource();
return source != null && source.getFeatures().length > 0;
}
if (layer instanceof WebGLPointsLayer) {
const source = layer.getSource();
return source != null && source.getFeatures().length > 0;
}
if (layer instanceof TileLayer || layer instanceof ImageLayer) {
// For tile/image layers, check if they have a source
return Boolean(layer.getSource());
}
return false;
}

Loading…
Cancel
Save