diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 1370a2a26d5..9856c45f220 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -56,8 +56,8 @@ "@visx/xychart": "^3.5.1", "abortcontroller-polyfill": "^1.7.8", "ace-builds": "^1.36.3", - "ag-grid-community": "32.2.1", - "ag-grid-react": "32.2.1", + "ag-grid-community": "33.1.1", + "ag-grid-react": "33.1.1", "antd": "4.10.3", "antd-v5": "npm:antd@^5.18.0", "bootstrap": "^3.4.1", @@ -14444,29 +14444,32 @@ } }, "node_modules/ag-charts-types": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-10.2.0.tgz", - "integrity": "sha512-PUqH1QtugpYLnlbMdeSZVf5PpT1XZVsP69qN1JXhetLtQpVC28zaj7ikwu9CMA9N9b+dBboA9QcjUQUJZVUokQ==" + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-11.1.1.tgz", + "integrity": "sha512-bRmUcf5VVhEEekhX8Vk0NSwa8Te8YM/zchjyYKR2CX4vDYiwoohM1Jg9RFvbIhVbLC1S6QrPEbx5v2C6RDfpSA==", + "license": "MIT" }, "node_modules/ag-grid-community": { - "version": "32.2.1", - "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-32.2.1.tgz", - "integrity": "sha512-mrnm1DnLI9Wd408mMwP+6p7lbTC3FYgzNIUPygBvNh3SzZnbzTEUJF/BTKXi+MARWtG5S0IMUYy4hqBiLbobaQ==", + "version": "33.1.1", + "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-33.1.1.tgz", + "integrity": "sha512-CNubIro0ipj4nfQ5WJPG9Isp7UI6MMDvNzrPdHNf3W+IoM8Uv3RUhjEn7xQqpQHuu6o/tMjrqpacipMUkhzqnw==", + "license": "MIT", "dependencies": { - "ag-charts-types": "10.2.0" + "ag-charts-types": "11.1.1" } }, "node_modules/ag-grid-react": { - "version": "32.2.1", - "resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-32.2.1.tgz", - "integrity": "sha512-lojTKsT/ncRZ81mrDa7qkIhZePfYlLCHIiAL1WbzL1mNPrglaa7QQKkE6hhhuAXvAm2uUhK1OfkMPnrqsEFldA==", + "version": "33.1.1", + "resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-33.1.1.tgz", + "integrity": "sha512-xJ+t2gpqUUwpFqAeDvKz/GLVR4unkOghfQBr8iIY9RAdGFarYFClJavsOa8XPVVUqEB9OIuPVFnOdtocbX0jeA==", + "license": "MIT", "dependencies": { - "ag-grid-community": "32.2.1", + "ag-grid-community": "33.1.1", "prop-types": "^15.8.1" }, "peerDependencies": { - "react": "^16.3.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.3.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/agent-base": { diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 242d4c0dec6..44d071a1357 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -123,8 +123,8 @@ "@visx/xychart": "^3.5.1", "abortcontroller-polyfill": "^1.7.8", "ace-builds": "^1.36.3", - "ag-grid-community": "32.2.1", - "ag-grid-react": "32.2.1", + "ag-grid-community": "33.1.1", + "ag-grid-react": "33.1.1", "antd": "4.10.3", "antd-v5": "npm:antd@^5.18.0", "bootstrap": "^3.4.1", diff --git a/superset-frontend/src/SqlLab/components/ResultSet/ResultSet.test.tsx b/superset-frontend/src/SqlLab/components/ResultSet/ResultSet.test.tsx index cbd3e10780f..03ebe37314e 100644 --- a/superset-frontend/src/SqlLab/components/ResultSet/ResultSet.test.tsx +++ b/superset-frontend/src/SqlLab/components/ResultSet/ResultSet.test.tsx @@ -27,6 +27,7 @@ import configureStore from 'redux-mock-store'; import { Store } from 'redux'; import thunk from 'redux-thunk'; import fetchMock from 'fetch-mock'; +import { setupAGGridModules } from 'src/setup/setupAGGridModules'; import ResultSet from 'src/SqlLab/components/ResultSet'; import { cachedQuery, @@ -143,6 +144,10 @@ const setup = (props?: any, store?: Store) => }); describe('ResultSet', () => { + beforeAll(() => { + setupAGGridModules(); + }); + // Add cleanup after each test afterEach(async () => { fetchMock.resetHistory(); @@ -363,7 +368,7 @@ describe('ResultSet', () => { ); }); const { getByRole } = setup(mockedProps, mockStore(initialState)); - expect(getByRole('treegrid')).toBeInTheDocument(); + expect(getByRole('grid')).toBeInTheDocument(); }); test('renders if there is a limit in query.results but not queryLimit', async () => { @@ -381,7 +386,7 @@ describe('ResultSet', () => { }, }), ); - expect(getByRole('treegrid')).toBeInTheDocument(); + expect(getByRole('grid')).toBeInTheDocument(); }); test('Async queries - renders "Fetch data preview" button when data preview has no results', () => { @@ -409,7 +414,7 @@ describe('ResultSet', () => { name: /fetch data preview/i, }), ).toBeVisible(); - expect(screen.queryByRole('treegrid')).not.toBeInTheDocument(); + expect(screen.queryByRole('grid')).not.toBeInTheDocument(); }); test('Async queries - renders "Refetch results" button when a query has no results', () => { @@ -438,7 +443,7 @@ describe('ResultSet', () => { name: /refetch results/i, }), ).toBeVisible(); - expect(screen.queryByRole('treegrid')).not.toBeInTheDocument(); + expect(screen.queryByRole('grid')).not.toBeInTheDocument(); }); test('Async queries - renders on the first call', () => { @@ -458,7 +463,7 @@ describe('ResultSet', () => { }, }), ); - expect(screen.getByRole('treegrid')).toBeVisible(); + expect(screen.getByRole('grid')).toBeVisible(); expect( screen.queryByRole('button', { name: /fetch data preview/i, diff --git a/superset-frontend/src/components/FilterableTable/FilterableTable.test.tsx b/superset-frontend/src/components/FilterableTable/FilterableTable.test.tsx index 349c390d5bd..8e07d0e97f4 100644 --- a/superset-frontend/src/components/FilterableTable/FilterableTable.test.tsx +++ b/superset-frontend/src/components/FilterableTable/FilterableTable.test.tsx @@ -24,8 +24,13 @@ import { userEvent, within, } from 'spec/helpers/testing-library'; +import { setupAGGridModules } from 'src/setup/setupAGGridModules'; describe('FilterableTable', () => { + beforeAll(() => { + setupAGGridModules(); + }); + const mockedProps = { orderedColumnKeys: ['a', 'b', 'c', 'children'], data: [ @@ -42,7 +47,7 @@ describe('FilterableTable', () => { const { getByRole, getByText } = render( , ); - expect(getByRole('treegrid')).toBeInTheDocument(); + expect(getByRole('grid')).toBeInTheDocument(); mockedProps.data.forEach(({ b: columnBContent }) => { expect(getByText(columnBContent)).toBeInTheDocument(); }); @@ -70,6 +75,10 @@ describe('FilterableTable', () => { }); describe('FilterableTable sorting - RTL', () => { + beforeAll(() => { + setupAGGridModules(); + }); + it('sorts strings correctly', () => { const stringProps = { orderedColumnKeys: ['columnA'], @@ -82,7 +91,7 @@ describe('FilterableTable sorting - RTL', () => { }; render(); - const stringColumn = within(screen.getByRole('treegrid')) + const stringColumn = within(screen.getByRole('grid')) .getByText('columnA') .closest('[role=button]'); const gridCells = screen.getByText('Bravo').closest('[role=rowgroup]'); @@ -127,7 +136,7 @@ describe('FilterableTable sorting - RTL', () => { }; render(); - const integerColumn = within(screen.getByRole('treegrid')) + const integerColumn = within(screen.getByRole('grid')) .getByText('columnB') .closest('[role=button]'); const gridCells = screen.getByText('21').closest('[role=rowgroup]'); @@ -162,7 +171,7 @@ describe('FilterableTable sorting - RTL', () => { }; render(); - const floatColumn = within(screen.getByRole('treegrid')) + const floatColumn = within(screen.getByRole('grid')) .getByText('columnC') .closest('[role=button]'); const gridCells = screen.getByText('45.67').closest('[role=rowgroup]'); @@ -217,7 +226,7 @@ describe('FilterableTable sorting - RTL', () => { }; render(); - const mixedFloatColumn = within(screen.getByRole('treegrid')) + const mixedFloatColumn = within(screen.getByRole('grid')) .getByText('columnD') .closest('[role=button]'); const gridCells = screen.getByText('48710.92').closest('[role=rowgroup]'); @@ -315,7 +324,7 @@ describe('FilterableTable sorting - RTL', () => { }; render(); - const dsColumn = within(screen.getByRole('treegrid')) + const dsColumn = within(screen.getByRole('grid')) .getByText('columnDS') .closest('[role=button]'); const gridCells = screen.getByText('2021-01-01').closest('[role=rowgroup]'); diff --git a/superset-frontend/src/components/GridTable/GridTable.test.tsx b/superset-frontend/src/components/GridTable/GridTable.test.tsx index 1f603c75f33..849831af6a2 100644 --- a/superset-frontend/src/components/GridTable/GridTable.test.tsx +++ b/superset-frontend/src/components/GridTable/GridTable.test.tsx @@ -17,6 +17,7 @@ * under the License. */ import { render } from 'spec/helpers/testing-library'; +import { setupAGGridModules } from 'src/setup/setupAGGridModules'; import GridTable from '.'; jest.mock('src/components/ErrorBoundary', () => ({ @@ -40,6 +41,10 @@ const mockedProps = { height: 500, }; +beforeAll(() => { + setupAGGridModules(); +}); + test('renders a grid with 3 Table rows', () => { const { queryByText } = render(); mockedProps.data.forEach(({ b: columnBContent }) => { diff --git a/superset-frontend/src/components/GridTable/index.tsx b/superset-frontend/src/components/GridTable/index.tsx index 1311148adc8..aab529fa569 100644 --- a/superset-frontend/src/components/GridTable/index.tsx +++ b/superset-frontend/src/components/GridTable/index.tsx @@ -16,11 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -import { useCallback, useMemo } from 'react'; +import { ReactNode, useCallback, useMemo } from 'react'; import { Global } from '@emotion/react'; import { css, useTheme } from '@superset-ui/core'; -import type { Column } from 'ag-grid-community'; +import type { Column, GridOptions } from 'ag-grid-community'; import { AgGridReact, type AgGridReactProps } from 'ag-grid-react'; import 'ag-grid-community/styles/ag-grid.css'; @@ -56,7 +56,7 @@ export interface TableProps { headerName?: string; width?: number; comparator?: (valueA: string | number, valueB: string | number) => number; - render?: (value: any) => React.ReactNode; + render?: (value: any) => ReactNode; }[]; size?: GridSize; @@ -159,15 +159,30 @@ function GridTable({ ].slice(showRowNumber ? 0 : 1), [rowIndexLength, columnReorderable, columns, showRowNumber, sortable], ); - const defaultColDef: AgGridReactProps['defaultColDef'] = { - ...(!columnReorderable && { suppressMovable: true }), - resizable: true, - sortable, - filter: Boolean(enableActions), - }; + const defaultColDef: AgGridReactProps['defaultColDef'] = useMemo( + () => ({ + ...(!columnReorderable && { suppressMovable: true }), + resizable: true, + sortable, + filter: Boolean(enableActions), + }), + [columnReorderable, enableActions, sortable], + ); const rowHeight = theme.gridUnit * (size === GridSize.Middle ? 9 : 7); + const gridOptions = useMemo( + () => ({ + enableCellTextSelection: true, + ensureDomOrder: true, + suppressFieldDotNotation: true, + headerHeight: rowHeight, + rowSelection: 'multiple', + rowHeight, + }), + [rowHeight], + ); + return ( ({ `} > ({ isExternalFilterPresent={isExternalFilterPresent} doesExternalFilterPass={externalFilter} components={gridComponents} - gridOptions={{ - enableCellTextSelection: true, - ensureDomOrder: true, - suppressFieldDotNotation: true, - headerHeight: rowHeight, - rowSelection: 'multiple', - rowHeight, - }} + gridOptions={gridOptions} onCellKeyDown={onKeyDown} /> diff --git a/superset-frontend/src/setup/setupAGGridModules.ts b/superset-frontend/src/setup/setupAGGridModules.ts new file mode 100644 index 00000000000..63a3d97a4e8 --- /dev/null +++ b/superset-frontend/src/setup/setupAGGridModules.ts @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + ModuleRegistry, + ColumnAutoSizeModule, + ColumnHoverModule, + RowAutoHeightModule, + RowStyleModule, + PaginationModule, + CellStyleModule, + TextFilterModule, + NumberFilterModule, + DateFilterModule, + ExternalFilterModule, + CsvExportModule, + ColumnApiModule, + RowApiModule, + CellApiModule, + RenderApiModule, + ClientSideRowModelModule, + CustomFilterModule, +} from 'ag-grid-community'; + +export const setupAGGridModules = () => { + ModuleRegistry.registerModules([ + ColumnAutoSizeModule, + ColumnHoverModule, + RowAutoHeightModule, + RowStyleModule, + PaginationModule, + CellStyleModule, + TextFilterModule, + NumberFilterModule, + DateFilterModule, + ExternalFilterModule, + CsvExportModule, + ColumnApiModule, + RowApiModule, + CellApiModule, + RenderApiModule, + ClientSideRowModelModule, + CustomFilterModule, + ]); +}; diff --git a/superset-frontend/src/views/App.tsx b/superset-frontend/src/views/App.tsx index 3d1cfc3e572..970ad958707 100644 --- a/superset-frontend/src/views/App.tsx +++ b/superset-frontend/src/views/App.tsx @@ -35,6 +35,7 @@ import getBootstrapData from 'src/utils/getBootstrapData'; import ToastContainer from 'src/components/MessageToasts/ToastContainer'; import setupApp from 'src/setup/setupApp'; import setupPlugins from 'src/setup/setupPlugins'; +import { setupAGGridModules } from 'src/setup/setupAGGridModules'; import { routes, isFrontendRoute } from 'src/views/routes'; import { Logger, LOG_ACTIONS_SPA_NAVIGATION } from 'src/logger/LogUtils'; import setupExtensions from 'src/setup/setupExtensions'; @@ -46,6 +47,7 @@ import { ScrollToTop } from './ScrollToTop'; setupApp(); setupPlugins(); setupExtensions(); +setupAGGridModules(); const bootstrapData = getBootstrapData();