Files
Jack Westbrook 6822805c32 Grafana Runtime: Use package.json exports for internal code (#102100)
* refactor(frontend): update runtime import paths for grafana/runtime/src -> grafana/runtime

* feat(runtime): introduce internal api entrypoint and exports property

* refactor(frontend): update runtime imports to use internal entrypoint

* chore(betterer): update results file

* refactor(bookmarks): update runtime/unstable import

* chore(betterer): update results file

* test(frontend): fix failing tests due to mocking nested runtime imports

* test(datasourcesrv): fix failing tests due to mocks

* chore(alerting): clean up redundant import

* fix(packages): fix default require export pointing to types declaration file

* docs(packages): update readme related to exports

* chore(internationalization): fix import paths

* chore(betterer): update results file
2025-03-27 08:59:57 +01:00

175 lines
4.5 KiB
TypeScript

import { Map as OpenLayersMap } from 'ol';
import { FeatureLike } from 'ol/Feature';
import LayerGroup from 'ol/layer/Group';
import WebGLPointsLayer from 'ol/layer/WebGLPoints';
import { Subject } from 'rxjs';
import { getFrameMatchers, MapLayerHandler, MapLayerOptions, PanelData, textUtil } from '@grafana/data';
import { config } from '@grafana/runtime';
import { GeomapPanel } from '../GeomapPanel';
import { MARKERS_LAYER_ID } from '../layers/data/markersLayer';
import { DEFAULT_BASEMAP_CONFIG, geomapLayerRegistry } from '../layers/registry';
import { MapLayerState } from '../types';
import { getNextLayerName } from './utils';
export const applyLayerFilter = (
handler: MapLayerHandler<unknown>,
options: MapLayerOptions<unknown>,
panelDataProps: PanelData
): void => {
if (handler.update) {
let panelData = panelDataProps;
if (options.filterData) {
const matcherFunc = getFrameMatchers(options.filterData);
panelData = {
...panelData,
series: panelData.series.filter(matcherFunc),
};
}
handler.update(panelData);
}
};
export async function updateLayer(panel: GeomapPanel, uid: string, newOptions: MapLayerOptions): Promise<boolean> {
if (!panel.map) {
return false;
}
const current = panel.byName.get(uid);
if (!current) {
return false;
}
let layerIndex = -1;
const group = panel.map?.getLayers()!;
for (let i = 0; i < group?.getLength(); i++) {
if (group.item(i) === current.layer) {
layerIndex = i;
break;
}
}
// Special handling for rename
if (newOptions.name !== uid) {
if (!newOptions.name) {
newOptions.name = uid;
} else if (panel.byName.has(newOptions.name)) {
return false;
}
panel.byName.delete(uid);
uid = newOptions.name;
panel.byName.set(uid, current);
}
// Type changed -- requires full re-initalization
if (current.options.type !== newOptions.type) {
// full init
} else {
// just update options
}
const layers = panel.layers.slice(0);
try {
const info = await initLayer(panel, panel.map, newOptions, current.isBasemap);
layers[layerIndex]?.handler.dispose?.();
layers[layerIndex] = info;
group.setAt(layerIndex, info.layer);
// initialize with new data
applyLayerFilter(info.handler, newOptions, panel.props.data);
} catch (err) {
console.warn('ERROR', err); // eslint-disable-line no-console
return false;
}
// Just to trigger a state update
panel.setState({ legends: [] });
panel.layers = layers;
panel.doOptionsUpdate(layerIndex);
return true;
}
export async function initLayer(
panel: GeomapPanel,
map: OpenLayersMap,
options: MapLayerOptions,
isBasemap?: boolean
): Promise<MapLayerState> {
if (isBasemap && (!options?.type || config.geomapDisableCustomBaseLayer)) {
options = DEFAULT_BASEMAP_CONFIG;
}
// Use default makers layer
if (!options?.type) {
options = {
type: MARKERS_LAYER_ID,
name: getNextLayerName(panel),
config: {},
};
}
const item = geomapLayerRegistry.getIfExists(options.type);
if (!item) {
return Promise.reject('unknown layer: ' + options.type);
}
if (options.config?.attribution) {
options.config.attribution = textUtil.sanitizeTextPanelContent(options.config.attribution);
}
const handler = await item.create(map, options, panel.props.eventBus, config.theme2);
const layer = handler.init(); // eslint-disable-line
if (options.opacity != null) {
layer.setOpacity(options.opacity);
}
if (!options.name) {
options.name = getNextLayerName(panel);
}
const UID = options.name;
const state: MapLayerState<unknown> = {
// UID, // unique name when added to the map (it may change and will need special handling)
isBasemap,
options,
layer,
handler,
mouseEvents: new Subject<FeatureLike | undefined>(),
getName: () => UID,
// Used by the editors
onChange: (cfg: MapLayerOptions) => {
updateLayer(panel, UID, cfg);
},
};
panel.byName.set(UID, state);
// eslint-disable-next-line
(state.layer as any).__state = state;
// Pass state into WebGLPointsLayers contained in a LayerGroup
if (layer instanceof LayerGroup) {
layer
.getLayers()
.getArray()
.forEach((layer) => {
if (layer instanceof WebGLPointsLayer) {
// eslint-disable-next-line
(layer as any).__state = state;
}
});
}
applyLayerFilter(handler, options, panel.props.data);
return state;
}
export const getMapLayerState = (l: any): MapLayerState => {
return l?.__state;
};