Compare commits

...

1 Commits

Author SHA1 Message Date
Superset Dev
1d136bc000 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 <noreply@anthropic.com>
2026-06-15 12:19:19 -07:00
4 changed files with 0 additions and 963 deletions

View File

@@ -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 = {

View File

@@ -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,

View File

@@ -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();
});

View File

@@ -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;