You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/06/15 19:20:08 UTC

(superset) 01/01: chore(sqllab): remove dead TableElement component and syncTable action

This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch chore/remove-dead-sqllab-tableelement
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 1d136bc0007378f861bd61e787d486b26cf59b28
Author: Superset Dev <de...@superset.apache.org>
AuthorDate: Mon Jun 15 12:19:19 2026 -0700

    chore(sqllab): remove dead TableElement component and syncTable action
    
    The SQL Lab left panel was rewritten from the flat `TableElement`
    schema list to the `TableExploreTree` (react-arborist) tree. The old
    `TableElement` component was unplugged from `SqlEditorLeftBar` but its
    files were never removed, leaving it referenced only by its own test.
    
    Its sole redux dependency, the `syncTable` thunk, became orphaned at the
    same time (the new tree refreshes via `fetchAndStoreTableSchema`), as did
    the `TableMetaData` interface local to `actions/sqlLab.ts` that only
    `syncTable` used. A separate `TableMetaData` in `hooks/apiResources` is
    the live type used everywhere else.
    
    Removes:
    - superset-frontend/src/SqlLab/components/TableElement/ (component + test)
    - syncTable thunk + its local TableMetaData interface + describe block
    
    Kept (still used by the new tree/preview): ColumnElement, ShowSQL,
    mergeTable/MERGE_TABLE and its reducer.
    
    Co-Authored-By: Claude Fable 5 <no...@anthropic.com>
---
 .../src/SqlLab/actions/sqlLab.test.ts              |  33 --
 superset-frontend/src/SqlLab/actions/sqlLab.ts     |  46 --
 .../components/TableElement/TableElement.test.tsx  | 464 ---------------------
 .../src/SqlLab/components/TableElement/index.tsx   | 420 -------------------
 4 files changed, 963 deletions(-)

diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.test.ts b/superset-frontend/src/SqlLab/actions/sqlLab.test.ts
index ace26784469..ae53c83b77a 100644
--- a/superset-frontend/src/SqlLab/actions/sqlLab.test.ts
+++ b/superset-frontend/src/SqlLab/actions/sqlLab.test.ts
@@ -1420,39 +1420,6 @@ describe('async actions', () => {
       });
     });
 
-    // eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
-    describe('syncTable', () => {
-      test('updates the table schema state in the backend', () => {
-        expect.assertions(4);
-
-        const tableName = 'table';
-        const schemaName = 'schema';
-        const store = mockStore(initialState);
-        const expectedActionTypes = [
-          actions.MERGE_TABLE, // syncTable
-        ];
-        const request = actions.syncTable(
-          query as any,
-          tableName as any,
-          schemaName,
-        );
-        return request(store.dispatch, store.getState, undefined).then(() => {
-          expect(store.getActions().map(a => a.type)).toEqual(
-            expectedActionTypes,
-          );
-          expect(store.getActions()[0].prepend).toBeFalsy();
-          expect(
-            fetchMock.callHistory.calls(updateTableSchemaEndpoint),
-          ).toHaveLength(1);
-
-          // tab state is not updated, since no query was run
-          expect(
-            fetchMock.callHistory.calls(updateTabStateEndpoint),
-          ).toHaveLength(0);
-        });
-      });
-    });
-
     // eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
     describe('runTablePreviewQuery', () => {
       const results = {
diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.ts b/superset-frontend/src/SqlLab/actions/sqlLab.ts
index 7058471a55b..072bfd05260 100644
--- a/superset-frontend/src/SqlLab/actions/sqlLab.ts
+++ b/superset-frontend/src/SqlLab/actions/sqlLab.ts
@@ -1346,52 +1346,6 @@ export function runTablePreviewQuery(
   };
 }
 
-export interface TableMetaData {
-  columns?: unknown[];
-  selectStar?: string;
-  primaryKey?: unknown;
-  foreignKeys?: unknown[];
-  indexes?: unknown[];
-}
-
-export function syncTable(
-  table: Table,
-  tableMetadata: TableMetaData,
-  finalQueryEditorId?: string,
-): SqlLabThunkAction<Promise<unknown>> {
-  return function (dispatch: AppDispatch) {
-    const finalTable = { ...table, queryEditorId: finalQueryEditorId };
-    const sync = isFeatureEnabled(FeatureFlag.SqllabBackendPersistence)
-      ? SupersetClient.post({
-          endpoint: encodeURI('/tableschemaview/'),
-          postPayload: { table: { ...tableMetadata, ...finalTable } },
-        })
-      : Promise.resolve({ json: { id: table.id } });
-
-    return sync
-      .then(({ json: resultJson }) => {
-        const newTable = { ...table, id: `${resultJson.id}` };
-        dispatch(
-          mergeTable({
-            ...newTable,
-            expanded: true,
-            initialized: true,
-          }),
-        );
-      })
-      .catch(() =>
-        dispatch(
-          addDangerToast(
-            t(
-              'An error occurred while fetching table metadata. ' +
-                'Please contact your administrator.',
-            ),
-          ),
-        ),
-      );
-  };
-}
-
 export function changeDataPreviewId(
   oldQueryId: string,
   newQuery: Query,
diff --git a/superset-frontend/src/SqlLab/components/TableElement/TableElement.test.tsx b/superset-frontend/src/SqlLab/components/TableElement/TableElement.test.tsx
deleted file mode 100644
index 990e6eb4488..00000000000
--- a/superset-frontend/src/SqlLab/components/TableElement/TableElement.test.tsx
+++ /dev/null
@@ -1,464 +0,0 @@
-/**
- * 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 { isValidElement } from 'react';
-import fetchMock from 'fetch-mock';
-import { FeatureFlag, isFeatureEnabled } from '@superset-ui/core';
-import TableElement, { Column } from 'src/SqlLab/components/TableElement';
-import { table, initialState } from 'src/SqlLab/fixtures';
-import { render, waitFor, fireEvent } from 'spec/helpers/testing-library';
-import * as sqlLabActions from 'src/SqlLab/actions/sqlLab';
-import { QueryEditor } from 'src/SqlLab/types';
-
-jest.mock('@superset-ui/core', () => ({
-  ...jest.requireActual('@superset-ui/core'),
-  isFeatureEnabled: jest.fn(),
-}));
-
-const mockedIsFeatureEnabled = isFeatureEnabled as jest.Mock;
-
-jest.mock('@superset-ui/core/components/Loading', () => ({
-  Loading: () => <div data-test="mock-loading" />,
-}));
-jest.mock('@superset-ui/core/components/IconTooltip', () => ({
-  IconTooltip: ({
-    onClick,
-    tooltip,
-  }: {
-    onClick: () => void;
-    tooltip: string;
-  }) => (
-    <button type="button" data-test="mock-icon-tooltip" onClick={onClick}>
-      {tooltip}
-    </button>
-  ),
-}));
-jest.mock(
-  'src/SqlLab/components/ColumnElement',
-  () =>
-    ({ column }: { column: Column }) => (
-      <div data-test="mock-column-element">{column.name}</div>
-    ),
-);
-const getTableMetadataEndpoint =
-  /\/api\/v1\/database\/\d+\/table_metadata\/(?:\?.*)?$/;
-const getExtraTableMetadataEndpoint =
-  /\/api\/v1\/database\/\d+\/table_metadata\/extra\/(?:\?.*)?$/;
-const updateTableSchemaExpandedEndpoint = 'glob:*/tableschemaview/*/expanded';
-const updateTableSchemaEndpoint = 'glob:*/tableschemaview/';
-
-beforeEach(() => {
-  fetchMock.get(getTableMetadataEndpoint, table);
-  fetchMock.get(getExtraTableMetadataEndpoint, {});
-  fetchMock.post(updateTableSchemaExpandedEndpoint, {});
-  fetchMock.post(updateTableSchemaEndpoint, {});
-});
-
-afterEach(() => fetchMock.clearHistory().removeRoutes());
-
-const mockedProps = {
-  table: {
-    ...table,
-    initialized: true,
-  },
-  activeKey: [table.id],
-};
-
-const createStateWithQueryEditor = (queryEditor: Partial<QueryEditor>) => ({
-  ...initialState,
-  sqlLab: {
-    ...initialState.sqlLab,
-    queryEditors: [queryEditor],
-  },
-});
-
-const setupSyncTableTest = () => {
-  const spy = jest.spyOn(sqlLabActions, 'syncTable');
-  mockedIsFeatureEnabled.mockImplementation(
-    featureFlag => featureFlag === FeatureFlag.SqllabBackendPersistence,
-  );
-  fetchMock.removeRoute(updateTableSchemaEndpoint);
-  fetchMock.post(
-    updateTableSchemaEndpoint,
-    { id: 100 },
-    { name: updateTableSchemaEndpoint },
-  );
-  return spy;
-};
-
-test('renders', () => {
-  expect(isValidElement(<TableElement table={table} />)).toBe(true);
-});
-
-test('renders with props', () => {
-  expect(isValidElement(<TableElement {...mockedProps} />)).toBe(true);
-});
-
-test('has 4 IconTooltip elements', async () => {
-  const { getAllByTestId } = render(<TableElement {...mockedProps} />, {
-    useRedux: true,
-    initialState,
-  });
-  await waitFor(() =>
-    expect(getAllByTestId('mock-icon-tooltip')).toHaveLength(6),
-  );
-});
-
-test('has 14 columns', async () => {
-  const { getAllByTestId } = render(<TableElement {...mockedProps} />, {
-    useRedux: true,
-    initialState,
-  });
-  await waitFor(() =>
-    expect(getAllByTestId('mock-column-element')).toHaveLength(14),
-  );
-});
-
-test('fades table', async () => {
-  const { getAllByTestId } = render(<TableElement {...mockedProps} />, {
-    useRedux: true,
-    initialState,
-  });
-  await waitFor(() =>
-    expect(getAllByTestId('mock-icon-tooltip')).toHaveLength(6),
-  );
-  const style = window.getComputedStyle(getAllByTestId('fade')[0]);
-  expect(style.opacity).toBe('0');
-  fireEvent.mouseEnter(getAllByTestId('table-element-header-container')[0]);
-  await waitFor(() =>
-    expect(window.getComputedStyle(getAllByTestId('fade')[0]).opacity).toBe(
-      '1',
-    ),
-  );
-});
-
-test('sorts columns', async () => {
-  const { getAllByTestId, getByText } = render(
-    <TableElement {...mockedProps} />,
-    {
-      useRedux: true,
-      initialState,
-    },
-  );
-  await waitFor(() =>
-    expect(getAllByTestId('mock-icon-tooltip')).toHaveLength(6),
-  );
-  expect(
-    getAllByTestId('mock-column-element').map(el => el.textContent),
-  ).toEqual(table.columns.map(col => col.name));
-  fireEvent.click(getByText('Sort columns alphabetically'));
-  const sorted = table.columns.map(col => col.name).sort();
-  expect(
-    getAllByTestId('mock-column-element').map(el => el.textContent),
-  ).toEqual(sorted);
-  expect(getAllByTestId('mock-column-element')[0]).toHaveTextContent('active');
-});
-
-test('removes the table', async () => {
-  const updateTableSchemaEndpoint = 'glob:*/tableschemaview/*';
-  fetchMock.delete(updateTableSchemaEndpoint, {});
-  mockedIsFeatureEnabled.mockImplementation(
-    featureFlag => featureFlag === FeatureFlag.SqllabBackendPersistence,
-  );
-  const { getAllByTestId, getByText } = render(
-    <TableElement {...mockedProps} />,
-    {
-      useRedux: true,
-      initialState,
-    },
-  );
-  await waitFor(() =>
-    expect(getAllByTestId('mock-icon-tooltip')).toHaveLength(6),
-  );
-  expect(fetchMock.callHistory.calls(updateTableSchemaEndpoint)).toHaveLength(
-    0,
-  );
-  fireEvent.click(getByText('Remove table preview'));
-  await waitFor(() =>
-    expect(fetchMock.callHistory.calls(updateTableSchemaEndpoint)).toHaveLength(
-      1,
-    ),
-  );
-  mockedIsFeatureEnabled.mockClear();
-});
-
-test('fetches table metadata when expanded', async () => {
-  render(<TableElement {...mockedProps} />, {
-    useRedux: true,
-    initialState,
-  });
-  expect(fetchMock.callHistory.calls(getTableMetadataEndpoint)).toHaveLength(0);
-  expect(
-    fetchMock.callHistory.calls(getExtraTableMetadataEndpoint),
-  ).toHaveLength(0);
-  await waitFor(() =>
-    expect(fetchMock.callHistory.calls(getTableMetadataEndpoint)).toHaveLength(
-      1,
-    ),
-  );
-  expect(
-    fetchMock.callHistory.calls(updateTableSchemaExpandedEndpoint),
-  ).toHaveLength(0);
-  expect(
-    fetchMock.callHistory.calls(getExtraTableMetadataEndpoint),
-  ).toHaveLength(1);
-});
-
-test('refreshes table metadata when triggered', async () => {
-  const { getAllByTestId, getByText } = render(
-    <TableElement {...mockedProps} />,
-    {
-      useRedux: true,
-      initialState,
-    },
-  );
-  await waitFor(() =>
-    expect(getAllByTestId('mock-icon-tooltip')).toHaveLength(6),
-  );
-  expect(fetchMock.callHistory.calls(updateTableSchemaEndpoint)).toHaveLength(
-    0,
-  );
-  expect(fetchMock.callHistory.calls(getTableMetadataEndpoint)).toHaveLength(1);
-
-  fireEvent.click(getByText('Refresh table schema'));
-  await waitFor(() =>
-    expect(fetchMock.callHistory.calls(getTableMetadataEndpoint)).toHaveLength(
-      2,
-    ),
-  );
-  await waitFor(() =>
-    expect(fetchMock.callHistory.calls(updateTableSchemaEndpoint)).toHaveLength(
-      1,
-    ),
-  );
-});
-
-test('calls syncTable with valid backend ID when query editor has tabViewId', async () => {
-  const syncTableSpy = setupSyncTableTest();
-  const testTable = {
-    ...table,
-    initialized: false,
-    queryEditorId: 'temp-id-123',
-  };
-
-  const state = createStateWithQueryEditor({
-    id: 'temp-id-123',
-    tabViewId: '42',
-    inLocalStorage: false,
-    name: 'Test Editor',
-  });
-
-  render(<TableElement table={testTable} activeKey={[testTable.id]} />, {
-    useRedux: true,
-    initialState: state,
-  });
-
-  await waitFor(() => {
-    expect(syncTableSpy).toHaveBeenCalledWith(
-      expect.any(Object),
-      expect.any(Object),
-      '42', // finalQueryEditorId
-    );
-  });
-
-  syncTableSpy.mockRestore();
-});
-
-test('does not call syncTable when query editor is in localStorage', async () => {
-  const syncTableSpy = setupSyncTableTest();
-  const testTable = {
-    ...table,
-    initialized: false,
-    queryEditorId: 'local-id',
-  };
-
-  const state = createStateWithQueryEditor({
-    id: 'local-id',
-    tabViewId: undefined,
-    inLocalStorage: true,
-    name: 'Local Editor',
-  });
-
-  render(<TableElement table={testTable} activeKey={[testTable.id]} />, {
-    useRedux: true,
-    initialState: state,
-  });
-
-  await waitFor(() => {
-    expect(fetchMock.callHistory.calls(getTableMetadataEndpoint)).toHaveLength(
-      1,
-    );
-  });
-
-  await new Promise(resolve => setTimeout(resolve, 100));
-  expect(syncTableSpy).not.toHaveBeenCalled();
-
-  syncTableSpy.mockRestore();
-});
-
-test('does not call syncTable with non-numeric queryEditorId', async () => {
-  const syncTableSpy = setupSyncTableTest();
-  const testTable = {
-    ...table,
-    initialized: false,
-    queryEditorId: 'not-a-number',
-  };
-
-  const state = createStateWithQueryEditor({
-    id: 'not-a-number',
-    tabViewId: 'also-not-a-number',
-    inLocalStorage: false,
-    name: 'Invalid Editor',
-  });
-
-  render(<TableElement table={testTable} activeKey={[testTable.id]} />, {
-    useRedux: true,
-    initialState: state,
-  });
-
-  await waitFor(() => {
-    expect(fetchMock.callHistory.calls(getTableMetadataEndpoint)).toHaveLength(
-      1,
-    );
-  });
-
-  await new Promise(resolve => setTimeout(resolve, 100));
-  expect(syncTableSpy).not.toHaveBeenCalled();
-
-  syncTableSpy.mockRestore();
-});
-
-test('does not call syncTable for already initialized tables', async () => {
-  const syncTableSpy = setupSyncTableTest();
-  const testTable = {
-    ...table,
-    initialized: true, // Already initialized
-    queryEditorId: '789',
-  };
-
-  const state = createStateWithQueryEditor({
-    id: '789',
-    tabViewId: '789',
-    inLocalStorage: false,
-    name: 'Initialized Editor',
-  });
-
-  render(<TableElement table={testTable} activeKey={[testTable.id]} />, {
-    useRedux: true,
-    initialState: state,
-  });
-
-  await waitFor(() => {
-    expect(fetchMock.callHistory.calls(getTableMetadataEndpoint)).toHaveLength(
-      1,
-    );
-  });
-
-  await new Promise(resolve => setTimeout(resolve, 100));
-  expect(syncTableSpy).not.toHaveBeenCalled();
-
-  syncTableSpy.mockRestore();
-});
-
-test('calls syncTable after query editor is migrated from localStorage', async () => {
-  const syncTableSpy = setupSyncTableTest();
-  const testTable = {
-    ...table,
-    initialized: false,
-    queryEditorId: 'temp-editor-id',
-  };
-
-  // Start with editor in localStorage
-  const localState = createStateWithQueryEditor({
-    id: 'temp-editor-id',
-    tabViewId: undefined,
-    inLocalStorage: true,
-    name: 'Temp Editor',
-  });
-
-  const { rerender } = render(
-    <TableElement table={testTable} activeKey={[testTable.id]} />,
-    {
-      useRedux: true,
-      initialState: localState,
-    },
-  );
-
-  await new Promise(resolve => setTimeout(resolve, 100));
-  expect(syncTableSpy).not.toHaveBeenCalled();
-
-  const migratedState = createStateWithQueryEditor({
-    id: 'temp-editor-id',
-    tabViewId: '999',
-    inLocalStorage: false,
-    name: 'Temp Editor',
-  });
-
-  rerender(<TableElement table={testTable} activeKey={[testTable.id]} />);
-
-  const { unmount } = render(
-    <TableElement table={testTable} activeKey={[testTable.id]} />,
-    {
-      useRedux: true,
-      initialState: migratedState,
-    },
-  );
-
-  await waitFor(() => {
-    expect(syncTableSpy).toHaveBeenCalledWith(
-      expect.any(Object),
-      expect.any(Object),
-      '999',
-    );
-  });
-
-  unmount();
-  syncTableSpy.mockRestore();
-});
-
-test('passes numeric queryEditorId validation', async () => {
-  const syncTableSpy = setupSyncTableTest();
-  const testTable = {
-    ...table,
-    initialized: false,
-    queryEditorId: 'editor-123',
-  };
-
-  const state = createStateWithQueryEditor({
-    id: 'editor-123',
-    tabViewId: '456',
-    inLocalStorage: false,
-    name: 'Valid Editor',
-  });
-
-  render(<TableElement table={testTable} activeKey={[testTable.id]} />, {
-    useRedux: true,
-    initialState: state,
-  });
-
-  await waitFor(() => {
-    expect(syncTableSpy).toHaveBeenCalled();
-    const [, , finalQueryEditorId] = syncTableSpy.mock.calls[0];
-    // Verify it's a valid numeric string
-    expect(Number.isNaN(Number(finalQueryEditorId))).toBe(false);
-    expect(typeof finalQueryEditorId).toBe('string');
-    expect(finalQueryEditorId).toMatch(/^\d+$/);
-  });
-
-  syncTableSpy.mockRestore();
-});
diff --git a/superset-frontend/src/SqlLab/components/TableElement/index.tsx b/superset-frontend/src/SqlLab/components/TableElement/index.tsx
deleted file mode 100644
index 6f113a21226..00000000000
--- a/superset-frontend/src/SqlLab/components/TableElement/index.tsx
+++ /dev/null
@@ -1,420 +0,0 @@
-/**
- * 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 { useState, useRef, useEffect } from 'react';
-import { useSelector } from 'react-redux';
-import { useAppDispatch } from 'src/SqlLab/hooks/useAppDispatch';
-import type { QueryEditor, SqlLabRootState, Table } from 'src/SqlLab/types';
-import {
-  ButtonGroup,
-  Card,
-  Collapse,
-  Tooltip,
-  Flex,
-  IconTooltip,
-  Loading,
-  ModalTrigger,
-  type CollapseProps,
-} from '@superset-ui/core/components';
-import { CopyToClipboard } from 'src/components';
-import { t } from '@apache-superset/core/translation';
-import { styled, useTheme } from '@apache-superset/core/theme';
-import { debounce } from 'lodash';
-
-import {
-  removeDataPreview,
-  removeTables,
-  addDangerToast,
-  syncTable,
-} from 'src/SqlLab/actions/sqlLab';
-import {
-  tableApiUtil,
-  useTableExtendedMetadataQuery,
-  useTableMetadataQuery,
-} from 'src/hooks/apiResources';
-import useEffectEvent from 'src/hooks/useEffectEvent';
-import { ActionType } from 'src/types/Action';
-import { Icons } from '@superset-ui/core/components/Icons';
-import { Space } from '@superset-ui/core/components/Space';
-import ColumnElement, { ColumnKeyTypeType } from '../ColumnElement';
-import ShowSQL from '../ShowSQL';
-
-export interface Column {
-  name: string;
-  keys?: { type: ColumnKeyTypeType }[];
-  type: string;
-}
-
-export interface TableElementProps extends CollapseProps {
-  table: Table;
-}
-
-const StyledSpan = styled.span`
-  cursor: pointer;
-`;
-
-const Fade = styled.div`
-  transition: all ${({ theme }) => theme.motionDurationMid};
-  opacity: ${(props: { hovered: boolean }) => (props.hovered ? 1 : 0)};
-`;
-
-const TableElement = ({ table, ...props }: TableElementProps) => {
-  const { dbId, catalog, schema, name, expanded, id } = table;
-  const theme = useTheme();
-  const dispatch = useAppDispatch();
-  const {
-    currentData: tableMetadata,
-    isSuccess: isMetadataSuccess,
-    isFetching: isMetadataFetching,
-    isError: hasMetadataError,
-  } = useTableMetadataQuery(
-    {
-      dbId,
-      catalog,
-      schema,
-      table: name,
-    },
-    { skip: !expanded },
-  );
-  const {
-    currentData: tableExtendedMetadata,
-    isSuccess: isExtraMetadataSuccess,
-    isLoading: isExtraMetadataLoading,
-    isError: hasExtendedMetadataError,
-  } = useTableExtendedMetadataQuery(
-    {
-      dbId,
-      catalog,
-      schema,
-      table: name,
-    },
-    { skip: !expanded },
-  );
-  const tableData = {
-    ...tableMetadata,
-    ...tableExtendedMetadata,
-  };
-  const queryEditors = useSelector<SqlLabRootState, QueryEditor[]>(
-    state => state.sqlLab.queryEditors,
-  );
-  const currentTable = { ...tableData, ...table };
-  const { queryEditorId } = currentTable;
-  const queryEditor = queryEditors.find(
-    qe => qe.id === queryEditorId || qe.tabViewId === queryEditorId,
-  );
-  const currentQueryEditorId = queryEditor?.tabViewId || queryEditorId;
-
-  useEffect(() => {
-    if (hasMetadataError || hasExtendedMetadataError) {
-      dispatch(
-        addDangerToast(t('An error occurred while fetching table metadata')),
-      );
-    }
-  }, [hasMetadataError, hasExtendedMetadataError, dispatch]);
-
-  // TODO: migrate syncTable logic by SIP-93
-  const syncTableMetadata = useEffectEvent(() => {
-    const { initialized } = table;
-    // if not a valid number, wait for backend to assign one
-    const hasFinalQueryEditorId =
-      currentQueryEditorId &&
-      !Number.isNaN(Number(currentQueryEditorId)) &&
-      currentTable.queryEditorId !== currentQueryEditorId;
-    if (!initialized && hasFinalQueryEditorId) {
-      dispatch(syncTable(currentTable, tableData, currentQueryEditorId));
-    }
-  });
-
-  useEffect(() => {
-    if (isMetadataSuccess && isExtraMetadataSuccess) {
-      syncTableMetadata();
-    }
-  }, [
-    isMetadataSuccess,
-    isExtraMetadataSuccess,
-    currentQueryEditorId,
-    syncTableMetadata,
-  ]);
-
-  const [sortColumns, setSortColumns] = useState(false);
-  const [hovered, setHovered] = useState(false);
-  const tableNameRef = useRef<HTMLInputElement>(null);
-
-  const setHover = (hovered: boolean) => {
-    debounce(() => setHovered(hovered), 100)();
-  };
-
-  const removeTable = () => {
-    dispatch(removeDataPreview(table));
-    dispatch(removeTables([table]));
-  };
-
-  const toggleSortColumns = () => {
-    setSortColumns(prevState => !prevState);
-  };
-
-  const refreshTableMetadata = () => {
-    dispatch(
-      tableApiUtil.invalidateTags([{ type: 'TableMetadatas', id: name }]),
-    );
-    dispatch(syncTable(table, tableData, table.queryEditorId));
-  };
-
-  const renderWell = () => {
-    let partitions;
-    let metadata;
-    if (tableData.partitions) {
-      let partitionQuery;
-      let partitionClipBoard;
-      if (tableData.partitions.partitionQuery) {
-        ({ partitionQuery } = tableData.partitions);
-        const tt = t('Copy partition query to clipboard');
-        partitionClipBoard = (
-          <CopyToClipboard
-            text={partitionQuery}
-            shouldShowText={false}
-            tooltipText={tt}
-            copyNode={<Icons.CopyOutlined iconSize="s" />}
-          />
-        );
-      }
-      const latest = Object.entries(tableData.partitions?.latest || [])
-        .map(([key, value]) => `${key}=${value}`)
-        .join('/');
-
-      partitions = (
-        <div>
-          <small>
-            {t('latest partition:')} {latest}
-          </small>{' '}
-          {partitionClipBoard}
-        </div>
-      );
-    }
-
-    if (tableData.metadata) {
-      metadata = Object.entries(tableData.metadata).map(([key, value]) => (
-        <div>
-          <small>
-            <strong>{key}:</strong> {value}
-          </small>
-        </div>
-      ));
-      if (!metadata?.length) {
-        // hide metadata card view
-        return null;
-      }
-    }
-
-    if (!partitions) {
-      // hide partition card view
-      return null;
-    }
-
-    return (
-      <Card size="small">
-        {partitions}
-        {metadata}
-      </Card>
-    );
-  };
-
-  const renderControls = () => {
-    let keyLink;
-    const KEYS_FOR_TABLE_TEXT = t('Keys for table');
-    if (tableData?.indexes?.length) {
-      keyLink = (
-        <ModalTrigger
-          modalTitle={`${KEYS_FOR_TABLE_TEXT} ${name}`}
-          modalBody={tableData.indexes.map((ix, i) => (
-            <pre key={i}>{JSON.stringify(ix, null, '  ')}</pre>
-          ))}
-          triggerNode={
-            <IconTooltip
-              className="pull-left"
-              tooltip={t('View keys & indexes (%s)', tableData.indexes.length)}
-            >
-              <Icons.TableOutlined
-                iconSize="m"
-                iconColor={theme.colorPrimary}
-              />
-            </IconTooltip>
-          }
-        />
-      );
-    }
-    return (
-      <Flex style={{ height: 22 }} align="center">
-        {isMetadataFetching || isExtraMetadataLoading ? (
-          <Loading position="inline" />
-        ) : (
-          <Fade
-            data-test="fade"
-            hovered={hovered}
-            onClick={e => e.stopPropagation()}
-          >
-            <ButtonGroup>
-              <Space size="small">
-                <IconTooltip
-                  className="pull-left pointer"
-                  onClick={refreshTableMetadata}
-                  tooltip={t('Refresh table schema')}
-                >
-                  <Icons.SyncOutlined
-                    iconSize="m"
-                    iconColor={theme.colorIcon}
-                  />
-                </IconTooltip>
-                {keyLink}
-                <IconTooltip
-                  onClick={toggleSortColumns}
-                  tooltip={
-                    sortColumns
-                      ? t('Original table column order')
-                      : t('Sort columns alphabetically')
-                  }
-                >
-                  <Icons.SortAscendingOutlined
-                    iconSize="m"
-                    aria-hidden
-                    iconColor={
-                      sortColumns ? theme.colorIcon : theme.colorTextDisabled
-                    }
-                  />
-                </IconTooltip>
-                {tableData.selectStar && (
-                  <CopyToClipboard
-                    copyNode={
-                      <IconTooltip
-                        aria-label={t('Copy')}
-                        tooltip={t('Copy SELECT statement to the clipboard')}
-                      >
-                        <Icons.CopyOutlined
-                          iconSize="m"
-                          aria-hidden
-                          iconColor={theme.colorIcon}
-                        />
-                      </IconTooltip>
-                    }
-                    text={tableData.selectStar}
-                    shouldShowText={false}
-                  />
-                )}
-                {tableData.view && (
-                  <ShowSQL
-                    sql={tableData.view}
-                    tooltipText={t('Show CREATE VIEW statement')}
-                    title={t('CREATE VIEW statement')}
-                  />
-                )}
-                <IconTooltip
-                  className=" table-remove pull-left pointer"
-                  onClick={removeTable}
-                  tooltip={t('Remove table preview')}
-                >
-                  <Icons.CloseOutlined
-                    iconSize="m"
-                    aria-hidden
-                    iconColor={theme.colorIcon}
-                  />
-                </IconTooltip>
-              </Space>
-            </ButtonGroup>
-          </Fade>
-        )}
-      </Flex>
-    );
-  };
-
-  const renderHeader = () => {
-    const element: HTMLInputElement | null = tableNameRef.current;
-    let trigger = [] as ActionType[];
-    if (element && element.offsetWidth < element.scrollWidth) {
-      trigger = ['hover'];
-    }
-
-    return (
-      <div
-        data-test="table-element-header-container"
-        className="clearfix header-container"
-      >
-        <Tooltip
-          id="copy-to-clipboard-tooltip"
-          style={{ cursor: 'pointer' }}
-          title={name}
-          trigger={trigger}
-        >
-          <StyledSpan
-            data-test="collapse"
-            ref={tableNameRef}
-            className="table-name"
-          >
-            <strong>{name}</strong>
-          </StyledSpan>
-        </Tooltip>
-      </div>
-    );
-  };
-
-  const renderBody = () => {
-    let cols;
-    if (tableData.columns) {
-      cols = tableData.columns.slice();
-      if (sortColumns) {
-        cols.sort((a: Column, b: Column) => {
-          const colA = a.name.toUpperCase();
-          const colB = b.name.toUpperCase();
-          return colA < colB ? -1 : colA > colB ? 1 : 0;
-        });
-      }
-    }
-
-    const metadata = (
-      <div data-test="table-element" css={{ paddingTop: 6 }}>
-        {renderWell()}
-        <div>
-          {cols?.map(col => (
-            <ColumnElement column={col} key={col.name} />
-          ))}
-        </div>
-      </div>
-    );
-    return metadata;
-  };
-
-  return (
-    <Collapse
-      activeKey={props.activeKey}
-      expandIconPosition="end"
-      onChange={props.onChange}
-      ghost
-      items={[
-        {
-          key: id,
-          label: renderHeader(),
-          children: renderBody(),
-          extra: renderControls(),
-          onMouseEnter: () => setHover(true),
-          onMouseLeave: () => setHover(false),
-        },
-      ]}
-    />
-  );
-};
-
-export default TableElement;