From fc4c699d85648b9fc3927e8dbd58d3737cf60eef Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 17 Dec 2025 09:21:39 +0000 Subject: [PATCH] Chore: More backwards compatible changes needed for react 19 (#115422) backwards compatible changes needed for react 19 --- eslint-suppressions.json | 5 ---- .../src/FlameGraph/FlameGraph.test.tsx | 6 ++-- .../src/FlameGraphContainer.test.tsx | 2 +- .../services/pluginExtensions/utils.test.tsx | 28 ++++++++----------- .../components/Select/resetSelectStyles.ts | 18 ++++++------ .../src/components/Table/Table.story.tsx | 11 +++++++- .../app/core/components/Login/LoginCtrl.tsx | 4 +-- .../rule-editor/CloneRuleEditor.test.tsx | 4 +-- .../app/features/explore/spec/query.test.tsx | 2 +- .../app/features/explore/spec/split.test.tsx | 2 +- .../logs/components/LogRowMenuCell.tsx | 7 +++-- .../app/plugins/panel/geomap/GeomapPanel.tsx | 12 +++++--- 12 files changed, 52 insertions(+), 49 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 61bc82f1a7b..cdbeabcc87b 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -763,11 +763,6 @@ "count": 1 } }, - "packages/grafana-ui/src/components/Select/resetSelectStyles.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, "packages/grafana-ui/src/components/Select/types.ts": { "@typescript-eslint/no-explicit-any": { "count": 6 diff --git a/packages/grafana-flamegraph/src/FlameGraph/FlameGraph.test.tsx b/packages/grafana-flamegraph/src/FlameGraph/FlameGraph.test.tsx index 64dc10da72e..b79074cfc6b 100644 --- a/packages/grafana-flamegraph/src/FlameGraph/FlameGraph.test.tsx +++ b/packages/grafana-flamegraph/src/FlameGraph/FlameGraph.test.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; -import * as React from 'react'; +import { type ComponentProps, useRef } from 'react'; import { createDataFrame } from '@grafana/data'; @@ -16,14 +16,14 @@ jest.mock('react-use', () => { return { ...reactUse, useMeasure: () => { - const ref = React.useRef(); + const ref = useRef(null); return [ref, { width: 1600 }]; }, }; }); describe('FlameGraph', () => { - function setup(props?: Partial>) { + function setup(props?: Partial>) { const flameGraphData = createDataFrame(data); const container = new FlameGraphDataContainer(flameGraphData, { collapsing: true }); diff --git a/packages/grafana-flamegraph/src/FlameGraphContainer.test.tsx b/packages/grafana-flamegraph/src/FlameGraphContainer.test.tsx index 4fdb00d6748..f7e7c09957c 100644 --- a/packages/grafana-flamegraph/src/FlameGraphContainer.test.tsx +++ b/packages/grafana-flamegraph/src/FlameGraphContainer.test.tsx @@ -21,7 +21,7 @@ jest.mock('@grafana/assistant', () => ({ jest.mock('react-use', () => ({ ...jest.requireActual('react-use'), useMeasure: () => { - const ref = useRef(); + const ref = useRef(null); return [ref, { width: 1600 }]; }, })); diff --git a/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx b/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx index b62ae46f27c..b139fa80a75 100644 --- a/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx +++ b/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx @@ -262,24 +262,18 @@ function createComponent( pluginId?: string, id?: string ): ComponentTypeWithExtensionMeta { - function ComponentWithMeta(props: Props) { - if (Implementation) { - return ; + const ComponentWithMeta: ComponentTypeWithExtensionMeta = Object.assign( + Implementation || (() =>
Test
), + { + meta: { + id: id ?? '', + pluginId: pluginId ?? '', + title: '', + description: '', + type: PluginExtensionTypes.component, + } satisfies PluginExtensionComponentMeta, } - - return
Test
; - } - - ComponentWithMeta.displayName = ''; - ComponentWithMeta.propTypes = {}; - ComponentWithMeta.contextTypes = {}; - ComponentWithMeta.meta = { - id: id ?? '', - pluginId: pluginId ?? '', - title: '', - description: '', - type: PluginExtensionTypes.component, - } satisfies PluginExtensionComponentMeta; + ); return ComponentWithMeta; } diff --git a/packages/grafana-ui/src/components/Select/resetSelectStyles.ts b/packages/grafana-ui/src/components/Select/resetSelectStyles.ts index 90ac3877c2a..1432c01e1da 100644 --- a/packages/grafana-ui/src/components/Select/resetSelectStyles.ts +++ b/packages/grafana-ui/src/components/Select/resetSelectStyles.ts @@ -1,9 +1,9 @@ import { useMemo } from 'react'; -import { CSSObjectWithLabel } from 'react-select'; +import { StylesConfig } from 'react-select'; import { GrafanaTheme2 } from '@grafana/data'; -export default function resetSelectStyles(theme: GrafanaTheme2) { +export default function resetSelectStyles(theme: GrafanaTheme2): Partial { return { clearIndicator: () => ({}), container: () => ({}), @@ -13,7 +13,7 @@ export default function resetSelectStyles(theme: GrafanaTheme2) { groupHeading: () => ({}), indicatorsContainer: () => ({}), indicatorSeparator: () => ({}), - input: function (originalStyles: CSSObjectWithLabel) { + input: function (originalStyles) { return { ...originalStyles, color: 'inherit', @@ -27,7 +27,7 @@ export default function resetSelectStyles(theme: GrafanaTheme2) { loadingIndicator: () => ({}), loadingMessage: () => ({}), menu: () => ({}), - menuList: ({ maxHeight }: { maxHeight: number }) => ({ + menuList: ({ maxHeight }) => ({ maxHeight, }), multiValue: () => ({}), @@ -38,7 +38,7 @@ export default function resetSelectStyles(theme: GrafanaTheme2) { multiValueRemove: () => ({}), noOptionsMessage: () => ({}), option: () => ({}), - placeholder: (originalStyles: CSSObjectWithLabel) => ({ + placeholder: (originalStyles) => ({ ...originalStyles, color: theme.colors.text.secondary, }), @@ -47,11 +47,11 @@ export default function resetSelectStyles(theme: GrafanaTheme2) { }; } -export function useCustomSelectStyles(theme: GrafanaTheme2, width: number | string | undefined) { +export function useCustomSelectStyles(theme: GrafanaTheme2, width: number | string | undefined): Partial { return useMemo(() => { return { ...resetSelectStyles(theme), - menuPortal: (base: CSSObjectWithLabel) => { + menuPortal: (base) => { // Would like to correct top position when menu is placed bottom, but have props are not sent to this style function. // Only state is. https://github.com/JedWatson/react-select/blob/master/packages/react-select/src/components/Menu.tsx#L605 return { @@ -60,7 +60,7 @@ export function useCustomSelectStyles(theme: GrafanaTheme2, width: number | stri }; }, //These are required for the menu positioning to function - menu: ({ top, bottom, position }: CSSObjectWithLabel) => { + menu: ({ top, bottom, position }) => { return { top, bottom, @@ -73,7 +73,7 @@ export function useCustomSelectStyles(theme: GrafanaTheme2, width: number | stri width: width ? theme.spacing(width) : '100%', display: width === 'auto' ? 'inline-flex' : 'flex', }), - option: (provided: CSSObjectWithLabel, state: any) => ({ + option: (provided, state) => ({ ...provided, opacity: state.isDisabled ? 0.5 : 1, }), diff --git a/packages/grafana-ui/src/components/Table/Table.story.tsx b/packages/grafana-ui/src/components/Table/Table.story.tsx index b6c97df317e..e3d5e4b56cc 100644 --- a/packages/grafana-ui/src/components/Table/Table.story.tsx +++ b/packages/grafana-ui/src/components/Table/Table.story.tsx @@ -263,7 +263,16 @@ export const Footer: StoryFn = (args) => { ); }; -export const Pagination: StoryFn = (args) => ; +export const Pagination: StoryFn = (args) => { + const theme = useTheme2(); + const data = buildData(theme, {}); + + return ( + + + + ); +}; Pagination.args = { enablePagination: true, }; diff --git a/public/app/core/components/Login/LoginCtrl.tsx b/public/app/core/components/Login/LoginCtrl.tsx index 1a5f3d572e4..1bab9faf64e 100644 --- a/public/app/core/components/Login/LoginCtrl.tsx +++ b/public/app/core/components/Login/LoginCtrl.tsx @@ -119,11 +119,11 @@ export const LoginCtrl = memo(({ resetCode, children }: Props) => { }, []); const login = useCallback( - (formModel: FormModel) => { + async (formModel: FormModel) => { setLoginErrorMessage(undefined); setIsLoggingIn(true); - getBackendSrv() + return getBackendSrv() .post('/login', formModel, { showErrorAlert: false }) .then((result) => { setResult(result); diff --git a/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx b/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx index 2572d9ddfc5..bc2153c38fe 100644 --- a/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx +++ b/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx @@ -165,10 +165,10 @@ describe('CloneRuleEditor', function () { ); await waitFor(() => { - expect(ui.inputs.name.get()).toHaveValue('First Ruler Rule (copy)'); + expect(ui.inputs.namespace.get()).toHaveTextContent('namespace-one'); }); + expect(ui.inputs.name.get()).toHaveValue('First Ruler Rule (copy)'); expect(ui.inputs.expr.get()).toHaveValue('vector(1) > 0'); - expect(ui.inputs.namespace.get()).toHaveTextContent('namespace-one'); expect(ui.inputs.group.get()).toHaveTextContent('group1'); expect( byRole('listitem', { diff --git a/public/app/features/explore/spec/query.test.tsx b/public/app/features/explore/spec/query.test.tsx index 2d2ca951ec0..1b185378ab0 100644 --- a/public/app/features/explore/spec/query.test.tsx +++ b/public/app/features/explore/spec/query.test.tsx @@ -50,7 +50,7 @@ describe('Explore: handle running/not running query', () => { jest.mocked(datasources.loki.query).mockReturnValueOnce(makeLogsQueryResponse()); // Make sure we render the logs panel - await screen.findByText(/^Logs$/); + await screen.findByRole('heading', { name: /^Logs$/ }); // Make sure we render the log line await screen.findByText(/custom log line/i); diff --git a/public/app/features/explore/spec/split.test.tsx b/public/app/features/explore/spec/split.test.tsx index e114201108a..0675a382d34 100644 --- a/public/app/features/explore/spec/split.test.tsx +++ b/public/app/features/explore/spec/split.test.tsx @@ -122,7 +122,7 @@ describe('Handles open/close splits and related events in UI and URL', () => { // Make sure we render the logs panel await waitFor(() => { - const logsPanels = screen.getAllByText(/^Logs$/); + const logsPanels = screen.getAllByRole('heading', { name: /^Logs$/ }); expect(logsPanels.length).toBe(2); }); diff --git a/public/app/features/logs/components/LogRowMenuCell.tsx b/public/app/features/logs/components/LogRowMenuCell.tsx index 892a5eea68b..e205c1608e1 100644 --- a/public/app/features/logs/components/LogRowMenuCell.tsx +++ b/public/app/features/logs/components/LogRowMenuCell.tsx @@ -185,11 +185,12 @@ export const LogRowMenuCell = memo( } ); -type AddonOnClickListener = (event: MouseEvent, row: LogRowModel) => void | undefined; +type AddonOnClickListener = (event: MouseEvent, row: LogRowModel) => void | undefined; +type ChildElementProps = Record & { onClick: AddonOnClickListener }; function addClickListenersToNode(nodes: ReactNode[], row: LogRowModel) { return nodes.map((node, index) => { - if (isValidElement(node)) { - const onClick: AddonOnClickListener = node.props.onClick; + if (isValidElement(node)) { + const onClick = node.props.onClick; if (!onClick) { return node; } diff --git a/public/app/plugins/panel/geomap/GeomapPanel.tsx b/public/app/plugins/panel/geomap/GeomapPanel.tsx index eb7d7c7dad4..cb532f0ed95 100644 --- a/public/app/plugins/panel/geomap/GeomapPanel.tsx +++ b/public/app/plugins/panel/geomap/GeomapPanel.tsx @@ -79,7 +79,7 @@ export class GeomapPanel extends Component { this.subs.add( this.props.eventBus.subscribe(PanelEditExitedEvent, (evt) => { if (this.mapDiv && this.props.id === evt.payload) { - this.initMapRef(this.mapDiv); + this.initMapAsync(this.mapDiv); } }) ); @@ -97,7 +97,7 @@ export class GeomapPanel extends Component { }); if (hasDependencies) { - this.initMapRef(this.mapDiv); + this.initMapAsync(this.mapDiv); } } }) @@ -182,7 +182,7 @@ export class GeomapPanel extends Component { if (noRepeatChanged) { if (this.mapDiv) { - this.initMapRef(this.mapDiv); + this.initMapAsync(this.mapDiv); } // Skip other options processing return; @@ -227,7 +227,7 @@ export class GeomapPanel extends Component { this.setState({ legends: this.getLegends() }); } - initMapRef = async (div: HTMLDivElement) => { + initMapAsync = async (div: HTMLDivElement | null) => { if (!div) { // Do not initialize new map or dispose old map return; @@ -437,6 +437,10 @@ export class GeomapPanel extends Component { return legends; } + initMapRef = (div: HTMLDivElement | null) => { + this.initMapAsync(div); + }; + render() { let { ttip, ttipOpen, topRight1, legends, topRight2 } = this.state; const { options } = this.props;