diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ea0e83e3baa..19592b554d6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -480,6 +480,7 @@ i18next.config.ts @grafana/grafana-frontend-platform /e2e-playwright/panels-suite/geomap-layer-types.spec.ts @grafana/dataviz-squad /e2e-playwright/panels-suite/geomap-map-controls.spec.ts @grafana/dataviz-squad /e2e-playwright/panels-suite/geomap-spatial-operations-transform.spec.ts @grafana/dataviz-squad +/e2e-playwright/panels-suite/heatmap.spec.ts @grafana/dataviz-squad /e2e-playwright/panels-suite/panelEdit_base.spec.ts @grafana/dashboards-squad /e2e-playwright/panels-suite/panelEdit_queries.spec.ts @grafana/dashboards-squad /e2e-playwright/panels-suite/panelEdit_transforms.spec.ts @grafana/datapro diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-heatmap/heatmap-x.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-heatmap/heatmap-x.v42.json index dab0251e18a..ba341da51ca 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-heatmap/heatmap-x.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-heatmap/heatmap-x.v42.json @@ -303,6 +303,70 @@ ], "title": "Cells heatmap", "type": "heatmap" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 17 + }, + "id": 7, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "show": true, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false + } + }, + "pluginVersion": "12.3.0-pre", + "title": "No data", + "type": "heatmap" } ], "refresh": "", @@ -320,4 +384,4 @@ "title": "Heatmap X axis", "uid": "5Y0jv6pVz", "weekStart": "" -} \ No newline at end of file +} diff --git a/devenv/dev-dashboards/panel-heatmap/heatmap-x.json b/devenv/dev-dashboards/panel-heatmap/heatmap-x.json index a43e78ddb69..f6bc6a51885 100644 --- a/devenv/dev-dashboards/panel-heatmap/heatmap-x.json +++ b/devenv/dev-dashboards/panel-heatmap/heatmap-x.json @@ -295,6 +295,71 @@ ], "title": "Cells heatmap", "type": "heatmap" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 17 + }, + "id": 7, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "show": true, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false + } + }, + "pluginVersion": "12.3.0-pre", + "targets": [], + "title": "No data", + "type": "heatmap" } ], "schemaVersion": 37, diff --git a/e2e-playwright/panels-suite/heatmap.spec.ts b/e2e-playwright/panels-suite/heatmap.spec.ts new file mode 100644 index 00000000000..4fe163bf23e --- /dev/null +++ b/e2e-playwright/panels-suite/heatmap.spec.ts @@ -0,0 +1,36 @@ +import { test, expect } from '@grafana/plugin-e2e'; + +const DASHBOARD_UID = '5Y0jv6pVz'; + +// test.use(); + +test.describe('Panels test: Heatmap', { tag: ['@panels', '@heatmap'] }, () => { + test('renders successfully', async ({ gotoDashboardPage, selectors, page }) => { + const dashboardPage = await gotoDashboardPage({ + uid: DASHBOARD_UID, + }); + + // check that gauges are rendered + const uplot = page.locator('.uplot'); + await expect(uplot, 'panels are rendered').toHaveCount(2); + + // check that no panel errors exist + const errorInfo = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.headerCornerInfo('error')); + await expect(errorInfo, 'no errors in the panels').toBeHidden(); + }); + + test('"no data"', async ({ gotoDashboardPage, selectors, page }) => { + const dashboardPage = await gotoDashboardPage({ + uid: DASHBOARD_UID, + queryParams: new URLSearchParams({ editPanel: '7' }), + }); + + const uplot = page.locator('.uplot'); + await expect(uplot, "that uplot doesn't appear").toBeHidden(); + + const emptyMessage = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.PanelDataErrorMessage); + await expect(emptyMessage, 'that the empty text appears').toHaveText('No data'); + }); + + // TODO tooltips, legends, and panel editing +}); diff --git a/eslint-suppressions.json b/eslint-suppressions.json index e068ca60eff..a3292e1a102 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -4751,14 +4751,6 @@ "count": 1 } }, - "public/app/plugins/panel/heatmap/HeatmapPanel.tsx": { - "@typescript-eslint/consistent-type-assertions": { - "count": 1 - }, - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, "public/app/plugins/panel/heatmap/migrations.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -4782,7 +4774,7 @@ }, "public/app/plugins/panel/heatmap/utils.ts": { "@typescript-eslint/consistent-type-assertions": { - "count": 17 + "count": 16 } }, "public/app/plugins/panel/histogram/Histogram.tsx": { diff --git a/public/app/plugins/panel/heatmap/HeatmapPanel.tsx b/public/app/plugins/panel/heatmap/HeatmapPanel.tsx index 78c3bb32d67..7a2c154351f 100644 --- a/public/app/plugins/panel/heatmap/HeatmapPanel.tsx +++ b/public/app/plugins/panel/heatmap/HeatmapPanel.tsx @@ -15,7 +15,7 @@ import { VizLayout, EventBusPlugin, } from '@grafana/ui'; -import { TimeRange2, TooltipHoverMode } from '@grafana/ui/internal'; +import { FacetedData, TimeRange2, TooltipHoverMode } from '@grafana/ui/internal'; import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; import { readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap'; @@ -23,39 +23,20 @@ import { AnnotationsPlugin2 } from '../timeseries/plugins/AnnotationsPlugin2'; import { OutsideRangePlugin } from '../timeseries/plugins/OutsideRangePlugin'; import { HeatmapTooltip } from './HeatmapTooltip'; -import { prepareHeatmapData } from './fields'; +import { HeatmapData, prepareHeatmapData } from './fields'; import { quantizeScheme } from './palettes'; import { Options } from './types'; import { prepConfig } from './utils'; interface HeatmapPanelProps extends PanelProps {} -export const HeatmapPanel = ({ - data, - id, - timeRange, - timeZone, - width, - height, - options, - fieldConfig, - eventBus, - onChangeTimeRange, - replaceVariables, -}: HeatmapPanelProps) => { +type HeatmapDataForViz = Required> & Omit; + +const shouldRenderViz = (info: HeatmapData): info is HeatmapDataForViz => !(info.warning || !info.heatmap); + +export const HeatmapPanel = (props: HeatmapPanelProps) => { + const { data, id, timeRange, options, fieldConfig, replaceVariables } = props; const theme = useTheme2(); - const styles = useStyles2(getStyles); - const { sync, eventsScope, canAddAnnotations, onSelectRange, canExecuteActions } = usePanelContext(); - const cursorSync = sync?.() ?? DashboardCursorSync.Off; - - const userCanExecuteActions = useMemo(() => canExecuteActions?.() ?? false, [canExecuteActions]); - - // temp range set for adding new annotation set by TooltipPlugin2, consumed by AnnotationPlugin2 - const [newAnnotationRange, setNewAnnotationRange] = useState(null); - - // ugh - let timeRangeRef = useRef(timeRange); - timeRangeRef.current = timeRange; const palette = useMemo(() => quantizeScheme(options.color, theme), [options.color, theme]); @@ -75,7 +56,49 @@ export const HeatmapPanel = ({ } }, [data.series, data.annotations, options, palette, theme, replaceVariables, timeRange]); - const facets = useMemo(() => { + if (!shouldRenderViz(info)) { + return ( + + ); + } + + return ; +}; + +const HeatmapPanelViz = ({ + data, + timeRange, + timeZone, + width, + height, + options, + eventBus, + onChangeTimeRange, + replaceVariables, + info, + palette, +}: HeatmapPanelProps & { info: HeatmapDataForViz; palette: string[] }) => { + const theme = useTheme2(); + const styles = useStyles2(getStyles); + const { sync, eventsScope, canAddAnnotations, onSelectRange, canExecuteActions } = usePanelContext(); + const cursorSync = sync?.() ?? DashboardCursorSync.Off; + + const userCanExecuteActions = useMemo(() => canExecuteActions?.() ?? false, [canExecuteActions]); + + // temp range set for adding new annotation set by TooltipPlugin2, consumed by AnnotationPlugin2 + const [newAnnotationRange, setNewAnnotationRange] = useState(null); + + // ugh + let timeRangeRef = useRef(timeRange); + timeRangeRef.current = timeRange; + + const facets = useMemo((): FacetedData => { let exemplarsXFacet: number[] | undefined = []; // "Time" field let exemplarsYFacet: Array = []; @@ -102,7 +125,7 @@ export const HeatmapPanel = ({ } } - return [null, info.heatmap?.fields.map((f) => f.values), [exemplarsXFacet, exemplarsYFacet]]; + return [null, info.heatmap.fields.map((f) => f.values), [exemplarsXFacet, exemplarsYFacet]]; }, [info.heatmap, info.exemplars]); // ugh @@ -130,7 +153,7 @@ export const HeatmapPanel = ({ }, [options, timeZone, data.structureRev, cursorSync]); const renderLegend = () => { - if (!info.heatmap || !options.legend.show) { + if (!options.legend.show) { return null; } @@ -161,25 +184,13 @@ export const HeatmapPanel = ({ ); }; - if (info.warning || !info.heatmap) { - return ( - - ); - } - const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations()); return ( <> {(vizWidth: number, vizHeight: number) => ( - + {cursorSync !== DashboardCursorSync.Off && ( )} diff --git a/public/app/plugins/panel/heatmap/utils.ts b/public/app/plugins/panel/heatmap/utils.ts index 11a1c03b7c7..873013357a5 100644 --- a/public/app/plugins/panel/heatmap/utils.ts +++ b/public/app/plugins/panel/heatmap/utils.ts @@ -423,11 +423,15 @@ export function prepConfig(opts: PrepConfigOpts) { : dataRef.current?.xLayout === HeatmapCellLayout.ge ? 1 : 0, - yAlign: ((dataRef.current?.yLayout === HeatmapCellLayout.le - ? -1 - : dataRef.current?.yLayout === HeatmapCellLayout.ge - ? 1 - : 0) * (yAxisReverse ? -1 : 1)) as -1 | 0 | 1, + yAlign: (() => { + const yAlign = + dataRef.current?.yLayout === HeatmapCellLayout.le + ? -1 + : dataRef.current?.yLayout === HeatmapCellLayout.ge + ? 1 + : 0; + return yAxisReverse ? (yAlign === -1 ? 1 : yAlign === 1 ? -1 : 0) : yAlign; + })(), ySizeDivisor, disp: { fill: {