Frontend service: Fix geomap assets not loading (#110146)

* attempting to "fix" geomap

* copy gazetteer/maps folders into dockerfile for frontend service

* add TODO comments

* remove unused import

* conditionally use public cdn path

* fix unit tests

* try refactor e2e test for better stability

* Revert "try refactor e2e test for better stability"

This reverts commit d966d68e15922613755e120f536bba5436c43d1f.

* safer

* use grafana_public_path
This commit is contained in:
Ashley Harrison
2025-08-29 16:29:57 +01:00
committed by GitHub
parent c9f815088a
commit 57db26a9bf
13 changed files with 76 additions and 34 deletions

View File

@ -3409,8 +3409,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "4"],
[0, 0, 0, "Do not use any type assertions.", "5"],
[0, 0, 0, "Do not use any type assertions.", "6"],
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
[0, 0, 0, "Unexpected any. Specify a different type.", "8"]
[0, 0, 0, "Unexpected any. Specify a different type.", "7"]
],
"public/app/plugins/datasource/graphite/configuration/ConfigEditor.tsx:5381": [
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"],

View File

@ -93,6 +93,8 @@ docker_build('grafana-fs-dev',
'public/dashboards',
'public/app/plugins',
'public/build/assets-manifest.json',
'public/gazetteer',
'public/maps',
],
# Sync paths are relative to the Tiltfile

View File

@ -22,6 +22,8 @@ COPY public/emails public/emails
COPY public/views public/views
COPY public/dashboards public/dashboards
COPY public/app/plugins public/app/plugins
COPY public/gazetteer public/gazetteer
COPY public/maps public/maps
ADD devenv/frontend-service/build/grafana bin/grafana

View File

@ -2,8 +2,6 @@ import { getCenterPointWGS84 } from 'app/features/transformers/spatial/utils';
import { getGazetteer } from './gazetteer';
let backendResults: Record<string, unknown> = { hello: 'world' };
const geojsonObject = {
type: 'FeatureCollection',
features: [
@ -43,20 +41,16 @@ const geojsonObject = {
],
};
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getBackendSrv: () => ({
get: jest.fn().mockResolvedValue(backendResults),
}),
}));
describe('Placename lookup from geojson format', () => {
beforeEach(() => {
backendResults = { hello: 'world' };
jest.spyOn(global, 'fetch').mockResolvedValue({
ok: true,
status: 200,
json: jest.fn().mockResolvedValue(geojsonObject),
} as unknown as Response);
});
it('can lookup by id', async () => {
backendResults = geojsonObject;
const gaz = await getGazetteer('local');
expect(gaz.error).toBeUndefined();
expect(getCenterPointWGS84(gaz.find('A')?.geometry())).toMatchInlineSnapshot(`
@ -67,7 +61,6 @@ describe('Placename lookup from geojson format', () => {
`);
});
it('can look up by a code', async () => {
backendResults = geojsonObject;
const gaz = await getGazetteer('airports');
expect(gaz.error).toBeUndefined();
expect(getCenterPointWGS84(gaz.find('B')?.geometry())).toMatchInlineSnapshot(`
@ -79,7 +72,6 @@ describe('Placename lookup from geojson format', () => {
});
it('can look up by an id property', async () => {
backendResults = geojsonObject;
const gaz = await getGazetteer('airports');
expect(gaz.error).toBeUndefined();
expect(getCenterPointWGS84(gaz.find('C')?.geometry())).toMatchInlineSnapshot(`

View File

@ -2,7 +2,6 @@ import { getCenter } from 'ol/extent';
import { Geometry, Point } from 'ol/geom';
import { DataFrame, Field, FieldType, KeyValue, toDataFrame } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import { frameFromGeoJSON } from '../format/geojson';
import { pointFieldFromLonLat, pointFieldFromGeohash } from '../format/utils';
@ -181,7 +180,7 @@ export function frameAsGazetter(frame: DataFrame, opts: { path: string; keys?: s
const registry: KeyValue<Gazetteer> = {};
export const COUNTRIES_GAZETTEER_PATH = 'public/gazetteer/countries.json';
export const COUNTRIES_GAZETTEER_PATH = `${window.__grafana_public_path__}build/gazetteer/countries.json`;
/**
* Given a path to a file return a cached lookup function
@ -196,7 +195,8 @@ export async function getGazetteer(path?: string): Promise<Gazetteer> {
if (!lookup) {
try {
// block the async function
const data = await getBackendSrv().get(path!);
const response = await fetch(path);
const data = await response.json();
lookup = loadGazetteer(path, data);
} catch (err) {
console.warn('Error loading placename lookup', path, err);

View File

@ -4,22 +4,18 @@ import countriesJSON from '../../../../gazetteer/countries.json';
import { getGazetteer } from './gazetteer';
let backendResults: Record<string, string> | Array<Record<string, unknown>> = { hello: 'world' };
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getBackendSrv: () => ({
get: jest.fn().mockResolvedValue(backendResults),
}),
}));
const backendResults: Record<string, string> | Array<Record<string, unknown>> = countriesJSON;
describe('Placename lookup from worldmap format', () => {
beforeEach(() => {
backendResults = { hello: 'world' };
jest.spyOn(global, 'fetch').mockResolvedValue({
ok: true,
status: 200,
json: jest.fn().mockResolvedValue(backendResults),
} as unknown as Response);
});
it('unified worldmap config', async () => {
backendResults = countriesJSON;
const gaz = await getGazetteer('countries');
expect(gaz.error).toBeUndefined();
expect(toLonLat(gaz.find('US')?.point()?.getCoordinates()!)).toMatchInlineSnapshot(`

View File

@ -10,10 +10,53 @@ const longitude = [0, -74.1];
const latitude = [0, 40.7];
const geohash = ['9q94r', 'dr5rs'];
const names = ['A', 'B'];
const geojsonObject = {
type: 'FeatureCollection',
features: [
{
id: 'A',
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [0, 0],
},
properties: {
hello: 'A',
},
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [1, 1],
},
properties: {
some_code: 'B',
hello: 'B',
},
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [2, 2],
},
properties: {
an_id: 'C',
hello: 'C',
},
},
],
};
describe('handle location parsing', () => {
beforeEach(() => {
jest.spyOn(console, 'warn').mockImplementation();
jest.spyOn(global, 'fetch').mockResolvedValue({
ok: true,
status: 200,
json: jest.fn().mockResolvedValue(geojsonObject),
} as unknown as Response);
});
it('auto should find geohash field', async () => {

View File

@ -180,7 +180,7 @@ export class GrafanaDatasource extends DataSourceWithBackend<GrafanaQuery> {
},
],
maxDataPoints,
} as any).pipe(
} as DataQueryRequest<GrafanaQuery>).pipe(
map((v) => {
const frame = v.data[0] ?? new MutableDataFrame();
return new DataFrameView<FileElement>(frame);

View File

@ -394,7 +394,7 @@ export class GeomapPanel extends Component<Props, State> {
);
}
this.mouseWheelZoom!.setActive(Boolean(options.mouseWheelZoom));
this.mouseWheelZoom?.setActive(Boolean(options.mouseWheelZoom));
if (options.showAttribution) {
this.map.addControl(new Attribution({ collapsed: true, collapsible: true }));

View File

@ -80,7 +80,7 @@ export const geojsonLayer: MapLayerRegistryItem<GeoJSONMapperConfig> = {
const interpolatedUrl = getTemplateSrv().replace(config.src || '');
const source = new VectorSource({
url: interpolatedUrl,
url: `${window.__grafana_public_path__}build/${interpolatedUrl.replace(/^(public\/)/, '')}`,
format: new GeoJSON(),
});

View File

@ -76,7 +76,7 @@ describe('Worldmap Migrations', () => {
"min": 2,
},
"symbol": {
"fixed": "img/icons/marker/circle.svg",
"fixed": "build/img/icons/marker/circle.svg",
"mode": "fixed",
},
"symbolAlign": {

View File

@ -74,7 +74,7 @@ export const defaultStyleConfig = Object.freeze({
opacity: 0.4,
symbol: {
mode: ResourceDimensionMode.Fixed,
fixed: 'img/icons/marker/circle.svg',
fixed: 'build/img/icons/marker/circle.svg',
},
symbolAlign: {
horizontal: HorizontalAlign.Center,

View File

@ -75,6 +75,14 @@ module.exports = {
from: 'public/img',
to: 'img',
},
{
from: 'public/maps',
to: 'maps',
},
{
from: 'public/gazetteer',
to: 'gazetteer',
},
],
}),
],