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;