Canvas: Fix image loading when icon element SVG defined by field mappings (#115748)

* chore(gdev-dashboard): minimal repro of escalation #19939 bug report

* fix(canvas): add branching logic to handle field mapping to icons case

* test(canvas): validate integration of canvas icon mappings

* refactor(resource-dimension): defensive against JS `undefined` in paths
This commit is contained in:
Jesse David Peterson
2026-01-05 10:48:16 -04:00
committed by GitHub
parent e8e2f95637
commit d8cdee80f0
6 changed files with 1388 additions and 3 deletions
@@ -0,0 +1,598 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations \u0026 Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"panels": [
{
"datasource": {
"type": "datasource",
"uid": "-- Mixed --"
},
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"1": {
"color": "green",
"icon": "img/icons/unicons/check-circle.svg",
"index": 0,
"text": "Success"
},
"2": {
"color": "orange",
"icon": "img/icons/unicons/exclamation-triangle.svg",
"index": 1,
"text": "Warning"
},
"3": {
"color": "red",
"icon": "img/icons/unicons/times-circle.svg",
"index": 2,
"text": "Error"
}
},
"type": "value"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "text"
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "success"
},
"properties": [
{
"id": "mappings",
"value": [
{
"options": {
"1": {
"color": "green",
"icon": "img/icons/unicons/check-circle.svg",
"index": 0,
"text": "Success"
}
},
"type": "value"
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "warning"
},
"properties": [
{
"id": "mappings",
"value": [
{
"options": {
"2": {
"color": "orange",
"icon": "img/icons/unicons/exclamation-triangle.svg",
"index": 1,
"text": "Warning"
}
},
"type": "value"
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "error"
},
"properties": [
{
"id": "mappings",
"value": [
{
"options": {
"3": {
"color": "red",
"icon": "img/icons/unicons/times-circle.svg",
"index": 2,
"text": "Error"
}
},
"type": "value"
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "unmapped"
},
"properties": [
{
"id": "mappings",
"value": [
{
"options": {
"1": {
"color": "green",
"icon": "img/icons/unicons/check-circle.svg",
"index": 0,
"text": "Success"
},
"2": {
"color": "orange",
"icon": "img/icons/unicons/exclamation-triangle.svg",
"index": 1,
"text": "Warning"
},
"3": {
"color": "red",
"icon": "img/icons/unicons/times-circle.svg",
"index": 2,
"text": "Error"
}
},
"type": "value"
}
]
}
]
}
]
},
"gridPos": {
"h": 12,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"inlineEditing": true,
"root": {
"background": {
"color": {
"fixed": "transparent"
}
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"elements": [
{
"config": {
"align": "center",
"color": {
"fixed": "text"
},
"size": 16,
"text": {
"fixed": "Field-based Icons (from value mappings):",
"mode": "fixed"
},
"valign": "middle"
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Header",
"placement": {
"height": 40,
"left": 20,
"top": 10,
"width": 400
},
"type": "text"
},
{
"config": {
"fill": {
"field": "success",
"fixed": "green"
},
"path": {
"field": "success",
"mode": "field"
}
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Success Icon",
"placement": {
"height": 50,
"left": 50,
"top": 60,
"width": 50
},
"type": "icon"
},
{
"config": {
"align": "center",
"color": {
"field": "success",
"fixed": "text"
},
"size": 12,
"text": {
"field": "success",
"mode": "field"
},
"valign": "middle"
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Success Text",
"placement": {
"height": 25,
"left": 30,
"top": 115,
"width": 90
},
"type": "text"
},
{
"config": {
"fill": {
"field": "warning",
"fixed": "orange"
},
"path": {
"field": "warning",
"mode": "field"
}
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Warning Icon",
"placement": {
"height": 50,
"left": 180,
"top": 60,
"width": 50
},
"type": "icon"
},
{
"config": {
"align": "center",
"color": {
"field": "warning",
"fixed": "text"
},
"size": 12,
"text": {
"field": "warning",
"mode": "field"
},
"valign": "middle"
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Warning Text",
"placement": {
"height": 25,
"left": 160,
"top": 115,
"width": 90
},
"type": "text"
},
{
"config": {
"fill": {
"field": "error",
"fixed": "red"
},
"path": {
"field": "error",
"mode": "field"
}
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Error Icon",
"placement": {
"height": 50,
"left": 310,
"top": 60,
"width": 50
},
"type": "icon"
},
{
"config": {
"align": "center",
"color": {
"field": "error",
"fixed": "text"
},
"size": 12,
"text": {
"field": "error",
"mode": "field"
},
"valign": "middle"
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Error Text",
"placement": {
"height": 25,
"left": 290,
"top": 115,
"width": 90
},
"type": "text"
},
{
"config": {
"fill": {
"field": "unmapped",
"fixed": "#808080"
},
"path": {
"field": "unmapped",
"fixed": "img/icons/unicons/question-circle.svg",
"mode": "field"
}
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Unmapped Icon",
"placement": {
"height": 50,
"left": 440,
"top": 60,
"width": 50
},
"type": "icon"
},
{
"config": {
"align": "center",
"color": {
"field": "unmapped",
"fixed": "text"
},
"size": 12,
"text": {
"fixed": "No mapping (14)",
"mode": "fixed"
},
"valign": "middle"
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Unmapped Text",
"placement": {
"height": 25,
"left": 410,
"top": 115,
"width": 110
},
"type": "text"
},
{
"config": {
"align": "center",
"color": {
"fixed": "text"
},
"size": 14,
"text": {
"fixed": "Fixed Relative Path:",
"mode": "fixed"
},
"valign": "middle"
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Relative Label",
"placement": {
"height": 30,
"left": 50,
"top": 170,
"width": 200
},
"type": "text"
},
{
"config": {
"fill": {
"fixed": "blue"
},
"path": {
"fixed": "img/icons/unicons/cloud.svg",
"mode": "fixed"
}
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Relative Icon",
"placement": {
"height": 50,
"left": 260,
"top": 165,
"width": 50
},
"type": "icon"
},
{
"config": {
"align": "center",
"color": {
"fixed": "text"
},
"size": 14,
"text": {
"fixed": "Fixed Absolute URL:",
"mode": "fixed"
},
"valign": "middle"
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Absolute Label",
"placement": {
"height": 30,
"left": 50,
"top": 240,
"width": 200
},
"type": "text"
},
{
"config": {
"fill": {
"fixed": "purple"
},
"path": {
"fixed": "https://grafana.com/static/assets/img/grafana_icon.svg",
"mode": "fixed"
}
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Absolute Icon",
"placement": {
"height": 50,
"left": 260,
"top": 235,
"width": 50
},
"type": "icon"
}
],
"name": "Canvas Root",
"placement": {
"height": 100,
"left": 0,
"top": 0,
"width": 100
},
"type": "frame"
}
},
"pluginVersion": "12.1.0",
"targets": [
{
"csvContent": "success\n1",
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "csv_content"
},
{
"csvContent": "warning\n2",
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "B",
"scenarioId": "csv_content"
},
{
"csvContent": "error\n3",
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "C",
"scenarioId": "csv_content"
},
{
"csvContent": "unmapped\n14",
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "D",
"scenarioId": "csv_content"
}
],
"title": "Various SVG icons",
"type": "canvas"
}
],
"refresh": "",
"schemaVersion": 42,
"tags": [
"canvas",
"icons",
"test",
"v2"
],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Panel tests - Canvas - Kitchen sink",
"uid": "canvas-icon-fix-test-v2",
"weekStart": ""
}
@@ -0,0 +1,582 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"panels": [
{
"datasource": {
"type": "datasource",
"uid": "-- Mixed --"
},
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"1": {
"color": "green",
"icon": "img/icons/unicons/check-circle.svg",
"index": 0,
"text": "Success"
},
"2": {
"color": "orange",
"icon": "img/icons/unicons/exclamation-triangle.svg",
"index": 1,
"text": "Warning"
},
"3": {
"color": "red",
"icon": "img/icons/unicons/times-circle.svg",
"index": 2,
"text": "Error"
}
},
"type": "value"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "text",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "success"
},
"properties": [
{
"id": "mappings",
"value": [
{
"options": {
"1": {
"color": "green",
"icon": "img/icons/unicons/check-circle.svg",
"index": 0,
"text": "Success"
}
},
"type": "value"
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "warning"
},
"properties": [
{
"id": "mappings",
"value": [
{
"options": {
"2": {
"color": "orange",
"icon": "img/icons/unicons/exclamation-triangle.svg",
"index": 1,
"text": "Warning"
}
},
"type": "value"
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "error"
},
"properties": [
{
"id": "mappings",
"value": [
{
"options": {
"3": {
"color": "red",
"icon": "img/icons/unicons/times-circle.svg",
"index": 2,
"text": "Error"
}
},
"type": "value"
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "unmapped"
},
"properties": [
{
"id": "mappings",
"value": [
{
"options": {
"1": {
"color": "green",
"icon": "img/icons/unicons/check-circle.svg",
"index": 0,
"text": "Success"
},
"2": {
"color": "orange",
"icon": "img/icons/unicons/exclamation-triangle.svg",
"index": 1,
"text": "Warning"
},
"3": {
"color": "red",
"icon": "img/icons/unicons/times-circle.svg",
"index": 2,
"text": "Error"
}
},
"type": "value"
}
]
}
]
}
]
},
"gridPos": {
"h": 12,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"inlineEditing": true,
"root": {
"background": {
"color": {
"fixed": "transparent"
}
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"elements": [
{
"config": {
"align": "center",
"color": {
"fixed": "text"
},
"size": 16,
"text": {
"fixed": "Field-based Icons (from value mappings):",
"mode": "fixed"
},
"valign": "middle"
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Header",
"placement": {
"height": 40,
"left": 20,
"top": 10,
"width": 400
},
"type": "text"
},
{
"config": {
"fill": {
"field": "success",
"fixed": "green"
},
"path": {
"field": "success",
"mode": "field"
}
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Success Icon",
"placement": {
"height": 50,
"left": 50,
"top": 60,
"width": 50
},
"type": "icon"
},
{
"config": {
"align": "center",
"color": {
"field": "success",
"fixed": "text"
},
"size": 12,
"text": {
"field": "success",
"mode": "field"
},
"valign": "middle"
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Success Text",
"placement": {
"height": 25,
"left": 30,
"top": 115,
"width": 90
},
"type": "text"
},
{
"config": {
"fill": {
"field": "warning",
"fixed": "orange"
},
"path": {
"field": "warning",
"mode": "field"
}
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Warning Icon",
"placement": {
"height": 50,
"left": 180,
"top": 60,
"width": 50
},
"type": "icon"
},
{
"config": {
"align": "center",
"color": {
"field": "warning",
"fixed": "text"
},
"size": 12,
"text": {
"field": "warning",
"mode": "field"
},
"valign": "middle"
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Warning Text",
"placement": {
"height": 25,
"left": 160,
"top": 115,
"width": 90
},
"type": "text"
},
{
"config": {
"fill": {
"field": "error",
"fixed": "red"
},
"path": {
"field": "error",
"mode": "field"
}
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Error Icon",
"placement": {
"height": 50,
"left": 310,
"top": 60,
"width": 50
},
"type": "icon"
},
{
"config": {
"align": "center",
"color": {
"field": "error",
"fixed": "text"
},
"size": 12,
"text": {
"field": "error",
"mode": "field"
},
"valign": "middle"
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Error Text",
"placement": {
"height": 25,
"left": 290,
"top": 115,
"width": 90
},
"type": "text"
},
{
"config": {
"fill": {
"field": "unmapped",
"fixed": "#808080"
},
"path": {
"field": "unmapped",
"fixed": "img/icons/unicons/question-circle.svg",
"mode": "field"
}
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Unmapped Icon",
"placement": {
"height": 50,
"left": 440,
"top": 60,
"width": 50
},
"type": "icon"
},
{
"config": {
"align": "center",
"color": {
"field": "unmapped",
"fixed": "text"
},
"size": 12,
"text": {
"fixed": "No mapping (14)",
"mode": "fixed"
},
"valign": "middle"
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Unmapped Text",
"placement": {
"height": 25,
"left": 410,
"top": 115,
"width": 110
},
"type": "text"
},
{
"config": {
"align": "center",
"color": {
"fixed": "text"
},
"size": 14,
"text": {
"fixed": "Fixed Relative Path:",
"mode": "fixed"
},
"valign": "middle"
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Relative Label",
"placement": {
"height": 30,
"left": 50,
"top": 170,
"width": 200
},
"type": "text"
},
{
"config": {
"fill": {
"fixed": "blue"
},
"path": {
"fixed": "img/icons/unicons/cloud.svg",
"mode": "fixed"
}
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Relative Icon",
"placement": {
"height": 50,
"left": 260,
"top": 165,
"width": 50
},
"type": "icon"
},
{
"config": {
"align": "center",
"color": {
"fixed": "text"
},
"size": 14,
"text": {
"fixed": "Fixed Absolute URL:",
"mode": "fixed"
},
"valign": "middle"
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Absolute Label",
"placement": {
"height": 30,
"left": 50,
"top": 240,
"width": 200
},
"type": "text"
},
{
"config": {
"fill": {
"fixed": "purple"
},
"path": {
"fixed": "https://grafana.com/static/assets/img/grafana_icon.svg",
"mode": "fixed"
}
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"name": "Absolute Icon",
"placement": {
"height": 50,
"left": 260,
"top": 235,
"width": 50
},
"type": "icon"
}
],
"name": "Canvas Root",
"placement": {
"height": 100,
"left": 0,
"top": 0,
"width": 100
},
"type": "frame"
}
},
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "csv_content",
"csvContent": "success\n1"
},
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "B",
"scenarioId": "csv_content",
"csvContent": "warning\n2"
},
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "C",
"scenarioId": "csv_content",
"csvContent": "error\n3"
},
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "D",
"scenarioId": "csv_content",
"csvContent": "unmapped\n14"
}
],
"title": "Various SVG icons",
"type": "canvas"
}
],
"schemaVersion": 39,
"tags": ["canvas", "icons", "test", "v2"],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Panel tests - Canvas - Kitchen sink",
"uid": "canvas-icon-fix-test-v2",
"version": 1,
"weekStart": ""
}
+1
View File
@@ -24,6 +24,7 @@
"canvas-connection-examples": (import '../dev-dashboards/panel-canvas/canvas-connection-examples.json'),
"canvas-datalinks": (import '../dev-dashboards/panel-canvas/canvas-datalinks.json'),
"canvas-examples": (import '../dev-dashboards/panel-canvas/canvas-examples.json'),
"canvas_kitchen_sink": (import '../dev-dashboards/panel-canvas/canvas_kitchen_sink.json'),
"color_modes": (import '../dev-dashboards/panel-common/color_modes.json'),
"config-from-query": (import '../dev-dashboards/transforms/config-from-query.json'),
"dashlist": (import '../dev-dashboards/panel-dashlist/dashlist.json'),
@@ -0,0 +1,99 @@
import { test, expect } from '@grafana/plugin-e2e';
const DASHBOARD_UID = 'canvas-icon-fix-test-v2';
const PANEL_TITLE = 'Various SVG icons';
test.describe('Canvas Panel - Icon Mappings', () => {
test('should render field-based icons from value mappings correctly', async ({ gotoDashboardPage, page }) => {
await test.step('Navigate to dashboard and wait for panel to load', async () => {
await gotoDashboardPage({ uid: DASHBOARD_UID });
await page.waitForSelector('svg', { timeout: 10000 });
});
await test.step('Verify value mapping text values are displayed', async () => {
await expect(page.getByText('Success')).toBeVisible();
await expect(page.getByText('Warning')).toBeVisible();
await expect(page.getByText('Error')).toBeVisible();
});
await test.step('Verify SVG icons rendered for mapped values', async () => {
const svgCount = await page.locator('svg').count();
expect(svgCount).toBeGreaterThanOrEqual(3);
});
});
test('should render fixed path icons correctly', async ({ gotoDashboardPage, page }) => {
await test.step('Set up network interception for absolute URL icon', async () => {
await page.route('https://grafana.com/static/assets/img/grafana_icon.svg', async (route) => {
const dummySvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<circle cx="12" cy="12" r="10" fill="purple"/>
<text x="12" y="16" text-anchor="middle" fill="white" font-size="10">TEST</text>
</svg>`;
await route.fulfill({
status: 200,
contentType: 'image/svg+xml',
body: dummySvg,
});
});
});
await test.step('Navigate to dashboard and wait for SVGs to load', async () => {
await gotoDashboardPage({ uid: DASHBOARD_UID });
await page.waitForSelector('svg:not([aria-hidden="true"])', { timeout: 10000 });
await page.waitForLoadState('networkidle', { timeout: 10000 });
});
await test.step('Verify at least 5 visible SVG icons are rendered (3 mapped + 2 fixed)', async () => {
const visibleSvgs = page.locator('svg:not([aria-hidden="true"])');
const svgCount = await visibleSvgs.count();
expect(svgCount).toBeGreaterThanOrEqual(5);
});
await test.step('Verify visible SVG icons have content', async () => {
const visibleSvgs = page.locator('svg:not([aria-hidden="true"])');
const count = await visibleSvgs.count();
for (let i = 0; i < Math.min(count, 5); i++) {
const svg = visibleSvgs.nth(i);
await expect(svg).toBeAttached();
const svgContent = await svg.innerHTML();
expect(svgContent.length).toBeGreaterThan(0);
}
});
});
test('should not make invalid requests for unmapped numeric values', async ({ gotoDashboardPage, page }) => {
const failedRequests: string[] = [];
await test.step('Set up network request monitoring', async () => {
page.on('requestfailed', (request) => {
const url = request.url();
if (url.match(/\/build\/\d+$/)) {
failedRequests.push(url);
}
});
});
await test.step('Navigate to dashboard and wait for loading', async () => {
await gotoDashboardPage({ uid: DASHBOARD_UID });
await page.waitForTimeout(2000);
});
await test.step('Verify no invalid numeric path requests were made', async () => {
expect(failedRequests).toHaveLength(0);
});
});
test('should display text values from value mappings correctly', async ({ gotoDashboardPage, page }) => {
await test.step('Navigate to dashboard', async () => {
await gotoDashboardPage({ uid: DASHBOARD_UID });
});
await test.step('Verify mapped text values are displayed', async () => {
await expect(page.getByText('Success')).toBeVisible();
await expect(page.getByText('Warning')).toBeVisible();
await expect(page.getByText('Error')).toBeVisible();
await expect(page.getByText('No mapping (14)')).toBeVisible();
});
});
});
@@ -103,6 +103,84 @@ describe('getResourceDimension', () => {
expect(getResourceDimension(frame, config).value()).toEqual('');
});
it('should handle numeric field values with icon from value mapping', () => {
const publicPath = 'https://grafana.fake/public/';
const frame = createDataFrame({
fields: [
{
name: 'status_field',
values: [1, 2, 3],
display: (v) => ({
text: v === 1 ? 'Success' : v === 2 ? 'Warning' : 'Error',
numeric: Number(v),
icon:
v === 1
? 'img/icons/unicons/check-circle.svg'
: v === 2
? 'img/icons/unicons/exclamation-triangle.svg'
: 'img/icons/unicons/times-circle.svg',
}),
},
],
});
const config = { mode: ResourceDimensionMode.Field, field: 'status_field', fixed: '' };
expect(getResourceDimension(frame, config).get(0)).toEqual(`${publicPath}build/img/icons/unicons/check-circle.svg`);
expect(getResourceDimension(frame, config).get(1)).toEqual(
`${publicPath}build/img/icons/unicons/exclamation-triangle.svg`
);
expect(getResourceDimension(frame, config).get(2)).toEqual(`${publicPath}build/img/icons/unicons/times-circle.svg`);
});
it('should return empty string for unmapped numeric values without icon', () => {
const frame = createDataFrame({
fields: [
{
name: 'status_field',
values: [14],
display: (v) => ({
text: String(v),
numeric: Number(v),
icon: undefined,
}),
},
],
});
const config = { mode: ResourceDimensionMode.Field, field: 'status_field', fixed: '' };
expect(getResourceDimension(frame, config).get(0)).toEqual('');
expect(getResourceDimension(frame, config).value()).toEqual('');
});
it('should handle mixed numeric values with partial mappings', () => {
const publicPath = 'https://grafana.fake/public/';
const frame = createDataFrame({
fields: [
{
name: 'status_field',
values: [1, 99, 2],
display: (v) => ({
text: v === 1 ? 'Success' : v === 2 ? 'Warning' : String(v),
numeric: Number(v),
icon:
v === 1
? 'img/icons/unicons/check-circle.svg'
: v === 2
? 'img/icons/unicons/exclamation-triangle.svg'
: undefined,
}),
},
],
});
const config = { mode: ResourceDimensionMode.Field, field: 'status_field', fixed: '' };
expect(getResourceDimension(frame, config).get(0)).toEqual(`${publicPath}build/img/icons/unicons/check-circle.svg`);
expect(getResourceDimension(frame, config).get(1)).toEqual('');
expect(getResourceDimension(frame, config).get(2)).toEqual(
`${publicPath}build/img/icons/unicons/exclamation-triangle.svg`
);
});
// TODO: write tests for mapping modes
});
@@ -125,4 +203,25 @@ describe('getPublicOrAbsoluteUrl', () => {
expect(getPublicOrAbsoluteUrl({ path: 'icon.png' })).toEqual('');
expect(getPublicOrAbsoluteUrl(['icon.png'])).toEqual('');
});
it('should handle undefined publicPath gracefully', () => {
const originalPath = window.__grafana_public_path__;
// @ts-ignore - Intentionally testing runtime edge case
window.__grafana_public_path__ = undefined;
expect(getPublicOrAbsoluteUrl('icon.png')).toEqual('/build/icon.png');
window.__grafana_public_path__ = originalPath;
});
it('should handle empty string publicPath gracefully', () => {
const originalPath = window.__grafana_public_path__;
window.__grafana_public_path__ = '';
expect(getPublicOrAbsoluteUrl('icon.png')).toEqual('/build/icon.png');
window.__grafana_public_path__ = originalPath;
});
});
+9 -3
View File
@@ -15,8 +15,9 @@ export function getPublicOrAbsoluteUrl(path: unknown): string {
// 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;
const publicPath = window.__grafana_public_path__ || '/';
return isUrl ? path : `${window.__grafana_public_path__}build/${path}`;
return isUrl ? path : `${publicPath}build/${path}`;
}
export function getResourceDimension(
@@ -56,11 +57,11 @@ export function getResourceDimension(
// mode === ResourceDimensionMode.Field case
const getImageOrIcon = (value: unknown): string => {
if (typeof value !== 'string') {
if (typeof value !== 'string' && typeof value !== 'number') {
return '';
}
let url = value;
let url = typeof value === 'string' ? value : '';
if (field && field.display) {
const displayValue = field.display(value);
if (displayValue.icon) {
@@ -68,6 +69,11 @@ export function getResourceDimension(
}
}
const noIconFound = !url;
if (noIconFound) {
return '';
}
return getPublicOrAbsoluteUrl(url);
};