mirror of https://github.com/grafana/grafana.git
Geomap: Add variable support for GeoJSON url (#104587)
* Geomap: Add variable support for GeoJSON url * Add a check for variable dependency * Add tests for hasVariableDependencies function * Update docs * Update docs/sources/panels-visualizations/visualizations/geomap/index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> --------- Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com>
This commit is contained in:
parent
a049ddece7
commit
3dc5742a75
|
|
@ -313,7 +313,7 @@ The GeoJSON layer allows you to select and load a static GeoJSON file from the f
|
|||
<!-- prettier-ignore-start -->
|
||||
| Option | Description |
|
||||
| ------ | ----------- |
|
||||
| GeoJSON URL | Provides a choice of GeoJSON files that ship with Grafana. |
|
||||
| GeoJSON URL | Provides a choice of GeoJSON files that are included with Grafana. You can also enter a URL manually, which supports variables. |
|
||||
| Default Style | Controls which styles to apply when no rules above match.<ul><li>**Color** - configures the color of the default style</li><li>**Opacity** - configures the default opacity</li></ul> |
|
||||
| Style Rules | Apply styles based on feature properties <ul><li>**Rule** - allows you to select a _feature_, _condition_, and _value_ from the GeoJSON file in order to define a rule. The trash bin icon can be used to delete the current rule.</li><li>**Color** - configures the color of the style for the current rule</li><li>**Opacity** - configures the transparency level for the current rule</li> |
|
||||
| Display tooltip | Allows you to toggle tooltips for the layer. |
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import { Subscription } from 'rxjs';
|
|||
import { DataHoverEvent, PanelData, PanelProps } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { PanelContext, PanelContextRoot } from '@grafana/ui';
|
||||
import { appEvents } from 'app/core/app_events';
|
||||
import { VariablesChanged } from 'app/features/variables/types';
|
||||
import { PanelEditExitedEvent } from 'app/types/events';
|
||||
|
||||
import { GeomapOverlay, OverlayProps } from './GeomapOverlay';
|
||||
|
|
@ -31,7 +33,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, hasVariableDependencies } from './utils/utils';
|
||||
import { centerPointRegistry, MapCenterID } from './view';
|
||||
|
||||
// Allows multiple panels to share the same view instance
|
||||
|
|
@ -72,6 +74,25 @@ export class GeomapPanel extends Component<Props, State> {
|
|||
}
|
||||
})
|
||||
);
|
||||
// Subscribe to variable changes
|
||||
this.subs.add(
|
||||
appEvents.subscribe(VariablesChanged, () => {
|
||||
if (this.mapDiv) {
|
||||
// Check if any of the map's layers are dependent on variables
|
||||
const hasDependencies = this.layers.some((layer) => {
|
||||
const config = layer.options.config;
|
||||
if (!config || typeof config !== 'object') {
|
||||
return false;
|
||||
}
|
||||
return hasVariableDependencies(config);
|
||||
});
|
||||
|
||||
if (hasDependencies) {
|
||||
this.initMapRef(this.mapDiv);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { ReplaySubject } from 'rxjs';
|
|||
import { map as rxjsmap, first } from 'rxjs/operators';
|
||||
|
||||
import { MapLayerRegistryItem, MapLayerOptions, GrafanaTheme2, EventBus } from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { ComparisonOperation } from '@grafana/schema';
|
||||
|
||||
import { GeomapStyleRulesEditor } from '../../editor/GeomapStyleRulesEditor';
|
||||
|
|
@ -75,8 +76,11 @@ export const geojsonLayer: MapLayerRegistryItem<GeoJSONMapperConfig> = {
|
|||
create: async (map: Map, options: MapLayerOptions<GeoJSONMapperConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
|
||||
const config = { ...defaultOptions, ...options.config };
|
||||
|
||||
// Interpolate variables in the URL
|
||||
const interpolatedUrl = getTemplateSrv().replace(config.src || '');
|
||||
|
||||
const source = new VectorSource({
|
||||
url: config.src,
|
||||
url: interpolatedUrl,
|
||||
format: new GeoJSON(),
|
||||
});
|
||||
|
||||
|
|
@ -194,6 +198,7 @@ export const geojsonLayer: MapLayerRegistryItem<GeoJSONMapperConfig> = {
|
|||
settings: {
|
||||
options: getPublicGeoJSONFiles() ?? [],
|
||||
allowCustomValue: true,
|
||||
supportVariables: true,
|
||||
},
|
||||
defaultValue: defaultOptions.src,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
// Mock the config module to avoid undefined panels error
|
||||
jest.mock('app/core/config', () => ({
|
||||
config: {
|
||||
panels: {
|
||||
debug: {
|
||||
state: 'alpha',
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// 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(),
|
||||
}));
|
||||
|
||||
// Mock the template service
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
getTemplateSrv: jest.fn(),
|
||||
}));
|
||||
|
||||
import { hasVariableDependencies } from './utils';
|
||||
|
||||
describe('hasVariableDependencies', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return true when object contains existing template variables', () => {
|
||||
const availableVariables = [{ name: 'variable' }];
|
||||
const mockTemplateSrv = {
|
||||
containsTemplate: jest.fn().mockImplementation((str) => {
|
||||
// Check if any of the available variables are in the string
|
||||
return availableVariables.some((v) => str.includes(`$${v.name}`));
|
||||
}),
|
||||
getVariables: jest.fn().mockReturnValue(availableVariables),
|
||||
};
|
||||
(getTemplateSrv as jest.Mock).mockReturnValue(mockTemplateSrv);
|
||||
|
||||
const obj = { key: '$variable' };
|
||||
expect(hasVariableDependencies(obj)).toBe(true);
|
||||
expect(mockTemplateSrv.containsTemplate).toHaveBeenCalledWith(JSON.stringify(obj));
|
||||
});
|
||||
|
||||
it('should return false when object contains non-existent template variables', () => {
|
||||
const availableVariables = [{ name: 'variable' }];
|
||||
const mockTemplateSrv = {
|
||||
containsTemplate: jest.fn().mockImplementation((str) => {
|
||||
return availableVariables.some((v) => str.includes(`$${v.name}`));
|
||||
}),
|
||||
getVariables: jest.fn().mockReturnValue(availableVariables),
|
||||
};
|
||||
(getTemplateSrv as jest.Mock).mockReturnValue(mockTemplateSrv);
|
||||
|
||||
const obj = { key: '$nonexistent' };
|
||||
expect(hasVariableDependencies(obj)).toBe(false);
|
||||
expect(mockTemplateSrv.containsTemplate).toHaveBeenCalledWith(JSON.stringify(obj));
|
||||
});
|
||||
|
||||
it('should return false when object does not contain template variables', () => {
|
||||
const mockTemplateSrv = {
|
||||
containsTemplate: jest.fn().mockReturnValue(false),
|
||||
getVariables: jest.fn().mockReturnValue([]),
|
||||
};
|
||||
(getTemplateSrv as jest.Mock).mockReturnValue(mockTemplateSrv);
|
||||
|
||||
const obj = { key: 'static value' };
|
||||
expect(hasVariableDependencies(obj)).toBe(false);
|
||||
expect(mockTemplateSrv.containsTemplate).toHaveBeenCalledWith(JSON.stringify(obj));
|
||||
});
|
||||
|
||||
it('should handle nested objects with existing template variables', () => {
|
||||
const availableVariables = [{ name: 'variable' }];
|
||||
const mockTemplateSrv = {
|
||||
containsTemplate: jest.fn().mockImplementation((str) => {
|
||||
return availableVariables.some((v) => str.includes(`$${v.name}`));
|
||||
}),
|
||||
getVariables: jest.fn().mockReturnValue(availableVariables),
|
||||
};
|
||||
(getTemplateSrv as jest.Mock).mockReturnValue(mockTemplateSrv);
|
||||
|
||||
const obj = {
|
||||
key: 'static value',
|
||||
nested: {
|
||||
anotherKey: '$variable',
|
||||
},
|
||||
};
|
||||
expect(hasVariableDependencies(obj)).toBe(true);
|
||||
expect(mockTemplateSrv.containsTemplate).toHaveBeenCalledWith(JSON.stringify(obj));
|
||||
});
|
||||
});
|
||||
|
|
@ -2,6 +2,7 @@ import { Map as OpenLayersMap } from 'ol';
|
|||
import { defaults as interactionDefaults } from 'ol/interaction';
|
||||
|
||||
import { DataFrame, GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { getColorDimension, getScalarDimension, getScaledDimension, getTextDimension } from 'app/features/dimensions';
|
||||
import { getGrafanaDatasource } from 'app/plugins/datasource/grafana/datasource';
|
||||
|
||||
|
|
@ -73,6 +74,15 @@ async function initGeojsonFiles() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an object contains any Grafana template variables
|
||||
* @param obj - The object to check for variables
|
||||
* @returns true if the object contains any template variables
|
||||
*/
|
||||
export const hasVariableDependencies = (obj: object): boolean => {
|
||||
return getTemplateSrv().containsTemplate(JSON.stringify(obj));
|
||||
};
|
||||
|
||||
export const getNewOpenLayersMap = (panel: GeomapPanel, options: Options, div: HTMLDivElement) => {
|
||||
const view = panel.initMapView(options.view);
|
||||
return (panel.map = new OpenLayersMap({
|
||||
|
|
|
|||
Loading…
Reference in New Issue