mirror of
https://github.com/apache/superset.git
synced 2026-06-01 05:39:17 +00:00
chore: Upgrade to React 18 (#38563)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Evan Rusackas <evan@preset.io>
This commit is contained in:
committed by
GitHub
parent
28239c18d4
commit
41a22d7918
@@ -65,7 +65,7 @@ const ContentWrapper = styled.div`
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
const AppLayout: React.FC = ({ children }) => {
|
||||
const AppLayout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||
const queryEditorId = useSelector<SqlLabRootState, string>(
|
||||
({ sqlLab: { tabHistory } }) => tabHistory.slice(-1)[0],
|
||||
);
|
||||
|
||||
@@ -19,16 +19,14 @@
|
||||
import { isValidElement } from 'react';
|
||||
import { render } from 'spec/helpers/testing-library';
|
||||
import ColumnElement from 'src/SqlLab/components/ColumnElement';
|
||||
import { mockedActions, table } from 'src/SqlLab/fixtures';
|
||||
import { table } from 'src/SqlLab/fixtures';
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('ColumnElement', () => {
|
||||
const mockedProps = {
|
||||
actions: mockedActions,
|
||||
column: table.columns[0],
|
||||
};
|
||||
test('is valid with props', () => {
|
||||
expect(isValidElement(<ColumnElement {...mockedProps} />)).toBe(true);
|
||||
expect(isValidElement(<ColumnElement column={table.columns[0]} />)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
test('renders a proper primary key', () => {
|
||||
const { container } = render(<ColumnElement column={table.columns[0]} />);
|
||||
|
||||
@@ -116,19 +116,31 @@ describe('EditorWrapper', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('skips rerendering for updating cursor position', () => {
|
||||
test('skips rerendering for updating cursor position', async () => {
|
||||
const store = createStore(initialState, reducerIndex);
|
||||
setup(defaultQueryEditor, store);
|
||||
|
||||
expect(MockEditorHost).toHaveBeenCalled();
|
||||
const renderCount = MockEditorHost.mock.calls.length;
|
||||
await waitFor(() => expect(MockEditorHost).toHaveBeenCalled());
|
||||
const renderCountBeforeCursor = MockEditorHost.mock.calls.length;
|
||||
const updatedCursorPosition = { row: 1, column: 9 };
|
||||
store.dispatch(
|
||||
queryEditorSetCursorPosition(defaultQueryEditor, updatedCursorPosition),
|
||||
act(() => {
|
||||
store.dispatch(
|
||||
queryEditorSetCursorPosition(defaultQueryEditor, updatedCursorPosition),
|
||||
);
|
||||
});
|
||||
// Cursor position change should NOT trigger a re-render
|
||||
expect(MockEditorHost).toHaveBeenCalledTimes(renderCountBeforeCursor);
|
||||
|
||||
const renderCountBeforeDb = MockEditorHost.mock.calls.length;
|
||||
act(() => {
|
||||
store.dispatch(queryEditorSetDb(defaultQueryEditor, 2));
|
||||
});
|
||||
// DB change SHOULD trigger a re-render
|
||||
await waitFor(() =>
|
||||
expect(MockEditorHost.mock.calls.length).toBeGreaterThan(
|
||||
renderCountBeforeDb,
|
||||
),
|
||||
);
|
||||
expect(MockEditorHost).toHaveBeenCalledTimes(renderCount);
|
||||
store.dispatch(queryEditorSetDb(defaultQueryEditor, 2));
|
||||
expect(MockEditorHost).toHaveBeenCalledTimes(renderCount + 1);
|
||||
});
|
||||
|
||||
test('clears selectedText when selection becomes empty', async () => {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { COMMON_ERR_MESSAGES } from '@superset-ui/core';
|
||||
import {
|
||||
createWrapper,
|
||||
@@ -121,7 +121,7 @@ test('skips fetching validation if validator is undefined', () => {
|
||||
});
|
||||
|
||||
test('returns validation if validator is configured', async () => {
|
||||
const { result, waitFor } = initialize(true);
|
||||
const { result } = initialize(true);
|
||||
await waitFor(() =>
|
||||
expect(fetchMock.callHistory.calls(queryValidationApiRoute)).toHaveLength(
|
||||
1,
|
||||
@@ -143,7 +143,7 @@ test('returns server error description', async () => {
|
||||
fetchMock.post(queryValidationApiRoute, {
|
||||
throws: new Error(errorMessage),
|
||||
});
|
||||
const { result, waitFor } = initialize(true);
|
||||
const { result } = initialize(true);
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(result.current.data).toEqual([
|
||||
@@ -164,7 +164,7 @@ test('returns session expire description when CSRF token expired', async () => {
|
||||
fetchMock.post(queryValidationApiRoute, {
|
||||
throws: new Error(errorMessage),
|
||||
});
|
||||
const { result, waitFor } = initialize(true);
|
||||
const { result } = initialize(true);
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(result.current.data).toEqual([
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { getExtensionsRegistry } from '@superset-ui/core';
|
||||
import {
|
||||
createWrapper,
|
||||
@@ -104,7 +104,7 @@ test('returns keywords including fetched function_names data', async () => {
|
||||
const dbFunctionNamesApiRoute = `glob:*/api/v1/database/${expectDbId}/function_names/`;
|
||||
fetchMock.get(dbFunctionNamesApiRoute, fakeFunctionNamesApiResult);
|
||||
|
||||
const { result, waitFor } = renderHook(
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useKeywords({
|
||||
queryEditorId: 'testqueryid',
|
||||
@@ -241,7 +241,7 @@ test('returns column keywords among selected tables', async () => {
|
||||
);
|
||||
});
|
||||
|
||||
const { result, waitFor } = renderHook(
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useKeywords({
|
||||
queryEditorId: expectQueryEditorId,
|
||||
@@ -302,7 +302,7 @@ test('returns long keywords with detail', async () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
const { result, waitFor } = renderHook(
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useKeywords({
|
||||
queryEditorId: 'testqueryid',
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { FC } from 'react';
|
||||
import { FC, ReactNode } from 'react';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { styled, css } from '@apache-superset/core/theme';
|
||||
import { ModalTrigger } from '@superset-ui/core/components';
|
||||
@@ -92,7 +92,7 @@ const ShortcutCode = styled.code`
|
||||
padding: ${({ theme }) => `${theme.sizeUnit}px ${theme.sizeUnit * 2}px`};
|
||||
`;
|
||||
|
||||
const KeyboardShortcutButton: FC<{}> = ({ children }) => (
|
||||
const KeyboardShortcutButton: FC<{ children?: ReactNode }> = ({ children }) => (
|
||||
<ModalTrigger
|
||||
modalTitle={t('Keyboard shortcuts')}
|
||||
modalBody={
|
||||
|
||||
@@ -39,7 +39,9 @@ import getBootstrapData from 'src/utils/getBootstrapData';
|
||||
|
||||
const SQL_LAB_URL = '/sqllab';
|
||||
|
||||
const PopEditorTab: React.FC = ({ children }) => {
|
||||
const PopEditorTab: React.FC<{ children?: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [queryEditorId, setQueryEditorId] = useState<string>();
|
||||
const { requestedQuery } = useLocationState();
|
||||
|
||||
@@ -50,14 +50,23 @@ import { StaticPosition, StyledTooltip, ModalResultSetWrapper } from './styles';
|
||||
|
||||
interface QueryTableQuery extends Omit<
|
||||
QueryResponse,
|
||||
'state' | 'sql' | 'progress' | 'results' | 'duration' | 'started'
|
||||
| 'state'
|
||||
| 'sql'
|
||||
| 'progress'
|
||||
| 'results'
|
||||
| 'duration'
|
||||
| 'started'
|
||||
| 'user'
|
||||
| 'db'
|
||||
> {
|
||||
state?: Record<string, any>;
|
||||
sql?: Record<string, any>;
|
||||
progress?: Record<string, any>;
|
||||
results?: Record<string, any>;
|
||||
state?: ReactNode;
|
||||
sql?: ReactNode;
|
||||
progress?: ReactNode;
|
||||
results?: ReactNode;
|
||||
duration?: ReactNode;
|
||||
started?: ReactNode;
|
||||
user?: ReactNode;
|
||||
db?: ReactNode;
|
||||
}
|
||||
|
||||
interface QueryTableProps {
|
||||
@@ -249,7 +258,7 @@ const QueryTable = ({
|
||||
|
||||
return queries
|
||||
.map(query => {
|
||||
const { state, sql, progress, ...rest } = query;
|
||||
const { state, sql, progress, results: _results, ...rest } = query;
|
||||
const q = rest as QueryTableQuery;
|
||||
|
||||
const status = statusAttributes[state] || statusAttributes.error;
|
||||
@@ -265,7 +274,7 @@ const QueryTable = ({
|
||||
buttonStyle="link"
|
||||
onClick={() => onUserClicked(q.userId)}
|
||||
>
|
||||
{q.user}
|
||||
{q.user as ReactNode}
|
||||
</Button>
|
||||
);
|
||||
q.db = (
|
||||
@@ -274,7 +283,7 @@ const QueryTable = ({
|
||||
buttonStyle="link"
|
||||
onClick={() => onDbClicked(q.dbId)}
|
||||
>
|
||||
{q.db}
|
||||
{q.db as ReactNode}
|
||||
</Button>
|
||||
);
|
||||
q.started = (
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useMemo, FC, ReactElement } from 'react';
|
||||
import { useMemo, FC, ReactElement, type ReactNode } from 'react';
|
||||
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { styled, useTheme, SupersetTheme } from '@apache-superset/core/theme';
|
||||
|
||||
import { Button, DropdownButton } from '@superset-ui/core/components';
|
||||
import { IconType, Icons } from '@superset-ui/core/components/Icons';
|
||||
import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import { detectOS } from 'src/utils/common';
|
||||
import { QueryButtonProps } from 'src/SqlLab/types';
|
||||
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
|
||||
@@ -45,9 +45,9 @@ const buildTextAndIcon = (
|
||||
shouldShowStopButton: boolean,
|
||||
selectedText: string | undefined,
|
||||
theme: SupersetTheme,
|
||||
): { text: string; icon?: IconType } => {
|
||||
): { text: string; icon?: ReactNode } => {
|
||||
let text = t('Run');
|
||||
let icon: IconType | undefined = <Icons.CaretRightOutlined />;
|
||||
let icon: ReactNode = <Icons.CaretRightOutlined />;
|
||||
if (selectedText) {
|
||||
text = t('Run selection');
|
||||
icon = <Icons.StepForwardOutlined />;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import * as reactRedux from 'react-redux';
|
||||
import { act } from 'react';
|
||||
import {
|
||||
cleanup,
|
||||
fireEvent,
|
||||
@@ -136,7 +137,7 @@ describe('SaveDatasetModal', () => {
|
||||
const overwriteRadioBtn = screen.getByRole('radio', {
|
||||
name: /overwrite existing/i,
|
||||
});
|
||||
userEvent.click(overwriteRadioBtn);
|
||||
await userEvent.click(overwriteRadioBtn);
|
||||
|
||||
// Overwrite confirmation button should be disabled at this point
|
||||
const overwriteConfirmationBtn = screen.getByRole('button', {
|
||||
@@ -146,15 +147,21 @@ describe('SaveDatasetModal', () => {
|
||||
|
||||
// Click the overwrite select component
|
||||
const select = screen.getByRole('combobox', { name: /existing dataset/i })!;
|
||||
userEvent.click(select);
|
||||
await userEvent.click(select);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByText('Loading...')).not.toBeVisible(),
|
||||
);
|
||||
// Advance timers to flush debounced fetches in AsyncSelect
|
||||
await act(async () => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const loading = screen.queryByText('Loading...');
|
||||
expect(loading === null || !loading.checkVisibility()).toBe(true);
|
||||
});
|
||||
|
||||
// Select the first "existing dataset" from the listbox
|
||||
const option = screen.getAllByText('coolest table 0')[1];
|
||||
userEvent.click(option);
|
||||
await userEvent.click(option);
|
||||
|
||||
// Overwrite button should now be enabled
|
||||
expect(overwriteConfirmationBtn).toBeEnabled();
|
||||
@@ -168,25 +175,31 @@ describe('SaveDatasetModal', () => {
|
||||
const overwriteRadioBtn = screen.getByRole('radio', {
|
||||
name: /overwrite existing/i,
|
||||
});
|
||||
userEvent.click(overwriteRadioBtn);
|
||||
await userEvent.click(overwriteRadioBtn);
|
||||
|
||||
// Click the overwrite select component
|
||||
const select = screen.getByRole('combobox', { name: /existing dataset/i });
|
||||
userEvent.click(select);
|
||||
await userEvent.click(select);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByText('Loading...')).not.toBeVisible(),
|
||||
);
|
||||
// Advance timers to flush debounced fetches in AsyncSelect
|
||||
await act(async () => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const loading = screen.queryByText('Loading...');
|
||||
expect(loading === null || !loading.checkVisibility()).toBe(true);
|
||||
});
|
||||
|
||||
// Select the first "existing dataset" from the listbox
|
||||
const option = screen.getAllByText('coolest table 0')[1];
|
||||
userEvent.click(option);
|
||||
await userEvent.click(option);
|
||||
|
||||
// Click the overwrite button to access the confirmation screen
|
||||
const overwriteConfirmationBtn = screen.getByRole('button', {
|
||||
name: /overwrite/i,
|
||||
});
|
||||
userEvent.click(overwriteConfirmationBtn);
|
||||
await userEvent.click(overwriteConfirmationBtn);
|
||||
|
||||
// Overwrite screen text
|
||||
expect(screen.getByText(/save or overwrite dataset/i)).toBeInTheDocument();
|
||||
|
||||
@@ -140,7 +140,7 @@ const SouthPane = ({
|
||||
logAction(LOG_ACTIONS_SQLLAB_SWITCH_SOUTH_PANE_TAB, { tab: id });
|
||||
};
|
||||
const removeTable = useCallback(
|
||||
(key, action) => {
|
||||
(key: string, action: string) => {
|
||||
if (action === 'remove') {
|
||||
const table = pinnedTables.find(
|
||||
({ dbId, catalog, schema, name }) =>
|
||||
|
||||
@@ -292,7 +292,10 @@ const SqlEditor: FC<Props> = ({
|
||||
const SqlFormExtension = extensionsRegistry.get('sqleditor.extension.form');
|
||||
|
||||
const startQuery = useCallback(
|
||||
(ctasArg = false, ctas_method = CtasEnum.Table) => {
|
||||
(
|
||||
ctasArg = false,
|
||||
ctas_method: (typeof CtasEnum)[keyof typeof CtasEnum] = CtasEnum.Table,
|
||||
) => {
|
||||
if (!database) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { createWrapper } from 'spec/helpers/testing-library';
|
||||
import { initialState, defaultQueryEditor } from 'src/SqlLab/fixtures';
|
||||
import * as localStorageHelpers from 'src/utils/localStorageHelpers';
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import { initialState, defaultQueryEditor } from 'src/SqlLab/fixtures';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { createWrapper } from 'spec/helpers/testing-library';
|
||||
|
||||
import useQueryEditor from '.';
|
||||
|
||||
Reference in New Issue
Block a user