FS: Canvas panel icons and background images are missing (#110316)

* fix(frontend-service): mount public/img dir, copy Canvas subdirs over

* fix(canvas): update icon selector to use enum type not magic string

* fix(canvas): update folder selector to use grafana path from window

* chore(todo): code comment on when/where to remove Dockerfile COPYing

* feat(resource-dimension): public asset URL should include build/ for CDN

* chore(todo): note where to remove -- Grafana -- ds dependency from later

* fix(canvas): update folder selector to use build/ in path to hit CDN

* test(resource-dimension): expect relative URLs to include build/ for CDN

* fix(geomap): load icons for legend from CDN friendly path as well

* chore(resource-dimensions): delete dead code
This commit is contained in:
Jesse David Peterson 2025-09-02 15:38:44 -04:00 committed by GitHub
parent 451d6abe15
commit 095d90ae71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 38 additions and 62 deletions

View File

@ -95,6 +95,8 @@ docker_build('grafana-fs-dev',
'public/build/assets-manifest.json', 'public/build/assets-manifest.json',
'public/gazetteer', 'public/gazetteer',
'public/maps', 'public/maps',
'public/img/bg',
'public/img/icons',
], ],
# Sync paths are relative to the Tiltfile # Sync paths are relative to the Tiltfile

View File

@ -1,20 +1,20 @@
FROM ubuntu:24.04 FROM ubuntu:24.04
RUN --mount=type=cache,target=/var/lib/apt/lists \ RUN --mount=type=cache,target=/var/lib/apt/lists \
--mount=type=cache,target=/var/cache/apt \ --mount=type=cache,target=/var/cache/apt \
set -eux; \ set -eux; \
apt-get update; \ apt-get update; \
apt-get install -y --no-install-recommends ca-certificates; \ apt-get install -y --no-install-recommends ca-certificates; \
update-ca-certificates update-ca-certificates
WORKDIR /grafana WORKDIR /grafana
RUN mkdir -p "conf/provisioning/datasources" \ RUN mkdir -p "conf/provisioning/datasources" \
"conf/provisioning/dashboards" \ "conf/provisioning/dashboards" \
"conf/provisioning/notifiers" \ "conf/provisioning/notifiers" \
"conf/provisioning/plugins" \ "conf/provisioning/plugins" \
"conf/provisioning/access-control" \ "conf/provisioning/access-control" \
"conf/provisioning/alerting" "conf/provisioning/alerting"
COPY conf/defaults.ini conf/defaults.ini COPY conf/defaults.ini conf/defaults.ini
@ -22,8 +22,12 @@ COPY public/emails public/emails
COPY public/views public/views COPY public/views public/views
COPY public/dashboards public/dashboards COPY public/dashboards public/dashboards
COPY public/app/plugins public/app/plugins COPY public/app/plugins public/app/plugins
# TODO: Remove below as part of https://github.com/grafana/grafana/issues/110350
COPY public/gazetteer public/gazetteer COPY public/gazetteer public/gazetteer
COPY public/maps public/maps COPY public/maps public/maps
COPY public/img/bg public/img/bg
COPY public/img/icons public/img/icons
ADD devenv/frontend-service/build/grafana bin/grafana ADD devenv/frontend-service/build/grafana bin/grafana

View File

@ -87,7 +87,7 @@ export const FolderPickerTab = (props: Props) => {
value: `${folder}/${item.name}`, value: `${folder}/${item.name}`,
label: item.name, label: item.name,
search: (idx ? item.name.substring(0, idx) : item.name).toLowerCase(), search: (idx ? item.name.substring(0, idx) : item.name).toLowerCase(),
imgUrl: `public/${folder}/${item.name}`, imgUrl: `${window.__grafana_public_path__}build/${folder}/${item.name}`,
}); });
} }
}); });

View File

@ -1,43 +0,0 @@
import { useState, useEffect } from 'react';
import { SelectableValue } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import { Select } from '@grafana/ui';
interface Props {
value: string;
onChange: (v: string) => void;
}
const IconSelector = ({ value, onChange }: Props) => {
const [icons, setIcons] = useState<SelectableValue[]>(value ? [{ value, label: value }] : []);
const [icon, setIcon] = useState<string>();
const iconRoot = window.__grafana_public_path__ + 'img/icons/unicons/';
const onChangeIcon = (value: string) => {
onChange(value);
setIcon(value);
};
useEffect(() => {
getBackendSrv()
.get(`${iconRoot}/index.json`)
.then((data) => {
setIcons(
data.files.map((icon: string) => ({
value: icon,
label: icon,
}))
);
});
}, [iconRoot]);
return (
<Select
options={icons}
value={icon}
onChange={(selectedValue) => {
onChangeIcon(selectedValue.value!);
}}
/>
);
};
export default IconSelector;

View File

@ -3,17 +3,25 @@ import { ResourceDimensionMode } from '@grafana/schema';
import { getResourceDimension } from './resource'; import { getResourceDimension } from './resource';
describe('getResourceDimension', () => { describe('getResourceDimension', () => {
const publicPath = '/public/'; const publicPath = 'https://grafana.fake/public/';
beforeAll(() => { beforeAll(() => {
window.__grafana_public_path__ = publicPath; window.__grafana_public_path__ = publicPath;
}); });
it('fixed mode', () => { it('fixed relative path', () => {
const frame = undefined; const frame = undefined;
const fixedValue = 'img/icons/unicons/question-circle.svg'; const fixedValue = 'img/icons/unicons/question-circle.svg';
const config = { mode: ResourceDimensionMode.Fixed, fixed: fixedValue }; const config = { mode: ResourceDimensionMode.Fixed, fixed: fixedValue };
expect(getResourceDimension(frame, config).fixed).toEqual(publicPath + fixedValue); expect(getResourceDimension(frame, config).fixed).toEqual(`${publicPath}build/${fixedValue}`);
});
it('fixed full URL path', () => {
const frame = undefined;
const fixedUrlValue = 'https://3rdparty.fake/image.png';
const config = { mode: ResourceDimensionMode.Fixed, fixed: fixedUrlValue };
expect(getResourceDimension(frame, config).fixed).toEqual(fixedUrlValue);
}); });
// TODO: write tests for field and mapping modes // TODO: write tests for field and mapping modes

View File

@ -7,11 +7,16 @@ import { findField, getLastNotNullFieldValue } from './utils';
//--------------------------------------------------------- //---------------------------------------------------------
// Resource dimension // Resource dimension
//--------------------------------------------------------- //---------------------------------------------------------
export function getPublicOrAbsoluteUrl(v: string): string { export function getPublicOrAbsoluteUrl(path: string): string {
if (!v) { if (!path) {
return ''; return '';
} }
return v.indexOf(':/') > 0 ? v : window.__grafana_public_path__ + v;
// NOTE: The value of `path` could be either an URL string or a relative
// path to a Grafana CDN asset served from the CDN.
const isUrl = path.indexOf(':/') > 0;
return isUrl ? path : `${window.__grafana_public_path__}build/${path}`;
} }
export function getResourceDimension( export function getResourceDimension(

View File

@ -65,7 +65,7 @@ export function MarkersLegend(props: MarkersLegendProps) {
<div className={style.layerName}>{layerName}</div> <div className={style.layerName}>{layerName}</div>
<div className={cx(style.layerBody, style.fixedColorContainer)}> <div className={cx(style.layerBody, style.fixedColorContainer)}>
<SanitizedSVG <SanitizedSVG
src={`public/${symbol}`} src={`${window.__grafana_public_path__}build/${symbol}`}
className={style.legendSymbol} className={style.legendSymbol}
title={t('geomap.markers-legend.title-symbol', 'Symbol')} title={t('geomap.markers-legend.title-symbol', 'Symbol')}
style={{ fill: color, opacity: opacity }} style={{ fill: color, opacity: opacity }}
@ -84,7 +84,7 @@ export function MarkersLegend(props: MarkersLegendProps) {
if (colorMode.isContinuous && colorMode.getColors) { if (colorMode.isContinuous && colorMode.getColors) {
const colors = colorMode.getColors(config.theme2); const colors = colorMode.getColors(config.theme2);
const colorRange = getMinMaxAndDelta(colorField); const colorRange = getMinMaxAndDelta(colorField);
// TODO: explore showing mean on the gradiant scale // TODO: explore showing mean on the gradient scale
// const stats = reduceField({ // const stats = reduceField({
// field: color.field!, // field: color.field!,
// reducers: [ // reducers: [