mirror of
https://github.com/apache/superset.git
synced 2024-09-06 05:47:43 -04:00
feat: Flow for tables that already have a dataset (#22136)
This commit is contained in:
parent
96de314c0c
commit
04b7a26365
@ -24,7 +24,7 @@ import DatasetPanel, {
|
||||
tableColumnDefinition,
|
||||
COLUMN_TITLE,
|
||||
} from './DatasetPanel';
|
||||
import { exampleColumns } from './fixtures';
|
||||
import { exampleColumns, exampleDataset } from './fixtures';
|
||||
import {
|
||||
SELECT_MESSAGE,
|
||||
CREATE_MESSAGE,
|
||||
@ -44,7 +44,7 @@ jest.mock(
|
||||
);
|
||||
|
||||
describe('DatasetPanel', () => {
|
||||
it('renders a blank state DatasetPanel', () => {
|
||||
test('renders a blank state DatasetPanel', () => {
|
||||
render(<DatasetPanel hasError={false} columnList={[]} loading={false} />);
|
||||
|
||||
const blankDatasetImg = screen.getByRole('img', { name: /empty/i });
|
||||
@ -65,7 +65,7 @@ describe('DatasetPanel', () => {
|
||||
expect(sqlLabLink).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders a no columns screen', () => {
|
||||
test('renders a no columns screen', () => {
|
||||
render(
|
||||
<DatasetPanel
|
||||
tableName="Name"
|
||||
@ -83,7 +83,7 @@ describe('DatasetPanel', () => {
|
||||
expect(noColumnsDescription).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders a loading screen', () => {
|
||||
test('renders a loading screen', () => {
|
||||
render(
|
||||
<DatasetPanel
|
||||
tableName="Name"
|
||||
@ -99,7 +99,7 @@ describe('DatasetPanel', () => {
|
||||
expect(blankDatasetTitle).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders an error screen', () => {
|
||||
test('renders an error screen', () => {
|
||||
render(
|
||||
<DatasetPanel
|
||||
tableName="Name"
|
||||
@ -115,7 +115,7 @@ describe('DatasetPanel', () => {
|
||||
expect(errorDescription).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders a table with columns displayed', async () => {
|
||||
test('renders a table with columns displayed', async () => {
|
||||
const tableName = 'example_name';
|
||||
render(
|
||||
<DatasetPanel
|
||||
@ -138,4 +138,23 @@ describe('DatasetPanel', () => {
|
||||
expect(screen.getByText(row.type)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('renders an info banner if table already has a dataset', async () => {
|
||||
render(
|
||||
<DatasetPanel
|
||||
tableName="example_table"
|
||||
hasError={false}
|
||||
columnList={exampleColumns}
|
||||
loading={false}
|
||||
datasets={exampleDataset}
|
||||
/>,
|
||||
);
|
||||
|
||||
// This is text in the info banner
|
||||
expect(
|
||||
await screen.findByText(
|
||||
/this table already has a dataset associated with it. you can only associate one dataset with a table./i,
|
||||
),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
@ -17,12 +17,14 @@
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { supersetTheme, t, styled } from '@superset-ui/core';
|
||||
import { t, styled, useTheme } from '@superset-ui/core';
|
||||
import Icons from 'src/components/Icons';
|
||||
import Alert from 'src/components/Alert';
|
||||
import Table, { ColumnsType, TableSize } from 'src/components/Table';
|
||||
import { alphabeticalSort } from 'src/components/Table/sorters';
|
||||
// @ts-ignore
|
||||
import LOADING_GIF from 'src/assets/images/loading.gif';
|
||||
import { DatasetObject } from 'src/views/CRUD/data/dataset/AddDataset/types';
|
||||
import { ITableColumn } from './types';
|
||||
import MessageContent from './MessageContent';
|
||||
|
||||
@ -53,43 +55,56 @@ const HALF = 0.5;
|
||||
const MARGIN_MULTIPLIER = 3;
|
||||
|
||||
const StyledHeader = styled.div<StyledHeaderProps>`
|
||||
${({ theme }) => `
|
||||
position: ${(props: StyledHeaderProps) => props.position};
|
||||
margin: ${({ theme }) => theme.gridUnit * MARGIN_MULTIPLIER}px;
|
||||
margin-top: ${({ theme }) => theme.gridUnit * (MARGIN_MULTIPLIER + 1)}px;
|
||||
font-size: ${({ theme }) => theme.gridUnit * 6}px;
|
||||
font-weight: ${({ theme }) => theme.typography.weights.medium};
|
||||
padding-bottom: ${({ theme }) => theme.gridUnit * MARGIN_MULTIPLIER}px;
|
||||
margin: ${theme.gridUnit * (MARGIN_MULTIPLIER + 1)}px
|
||||
${theme.gridUnit * MARGIN_MULTIPLIER}px
|
||||
${theme.gridUnit * MARGIN_MULTIPLIER}px
|
||||
${theme.gridUnit * (MARGIN_MULTIPLIER + 3)}px;
|
||||
font-size: ${theme.gridUnit * 6}px;
|
||||
font-weight: ${theme.typography.weights.medium};
|
||||
padding-bottom: ${theme.gridUnit * MARGIN_MULTIPLIER}px;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.anticon:first-of-type {
|
||||
margin-right: ${({ theme }) => theme.gridUnit * (MARGIN_MULTIPLIER + 1)}px;
|
||||
margin-right: ${theme.gridUnit * (MARGIN_MULTIPLIER + 1)}px;
|
||||
}
|
||||
|
||||
.anticon:nth-of-type(2) {
|
||||
margin-left: ${({ theme }) => theme.gridUnit * (MARGIN_MULTIPLIER + 1)}px;
|
||||
}
|
||||
margin-left: ${theme.gridUnit * (MARGIN_MULTIPLIER + 1)}px;
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
margin-left: ${({ theme }) => theme.gridUnit * MARGIN_MULTIPLIER}px;
|
||||
margin-bottom: ${({ theme }) => theme.gridUnit * MARGIN_MULTIPLIER}px;
|
||||
font-weight: ${({ theme }) => theme.typography.weights.bold};
|
||||
${({ theme }) => `
|
||||
margin-left: ${theme.gridUnit * (MARGIN_MULTIPLIER + 3)}px;
|
||||
margin-bottom: ${theme.gridUnit * MARGIN_MULTIPLIER}px;
|
||||
font-weight: ${theme.typography.weights.bold};
|
||||
`}
|
||||
`;
|
||||
|
||||
const LoaderContainer = styled.div`
|
||||
padding: ${({ theme }) => theme.gridUnit * 8}px
|
||||
${({ theme }) => theme.gridUnit * 6}px;
|
||||
|
||||
${({ theme }) => `
|
||||
padding: ${theme.gridUnit * 8}px
|
||||
${theme.gridUnit * 6}px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledLoader = styled.div`
|
||||
${({ theme }) => `
|
||||
max-width: 50%;
|
||||
width: ${LOADER_WIDTH}px;
|
||||
|
||||
@ -100,19 +115,23 @@ const StyledLoader = styled.div`
|
||||
|
||||
div {
|
||||
width: 100%;
|
||||
margin-top: ${({ theme }) => theme.gridUnit * MARGIN_MULTIPLIER}px;
|
||||
margin-top: ${theme.gridUnit * MARGIN_MULTIPLIER}px;
|
||||
text-align: center;
|
||||
font-weight: ${({ theme }) => theme.typography.weights.normal};
|
||||
font-size: ${({ theme }) => theme.typography.sizes.l}px;
|
||||
color: ${({ theme }) => theme.colors.grayscale.light1};
|
||||
font-weight: ${theme.typography.weights.normal};
|
||||
font-size: ${theme.typography.sizes.l}px;
|
||||
color: ${theme.colors.grayscale.light1};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const TableContainer = styled.div`
|
||||
${({ theme }) => `
|
||||
position: relative;
|
||||
margin: ${({ theme }) => theme.gridUnit * MARGIN_MULTIPLIER}px;
|
||||
margin: ${theme.gridUnit * MARGIN_MULTIPLIER}px;
|
||||
margin-left: ${theme.gridUnit * (MARGIN_MULTIPLIER + 3)}px;
|
||||
overflow: scroll;
|
||||
height: calc(100% - ${({ theme }) => theme.gridUnit * 36}px);
|
||||
height: calc(100% - ${theme.gridUnit * 36}px);
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
@ -123,6 +142,26 @@ const StyledTable = styled(Table)`
|
||||
right: 0;
|
||||
`;
|
||||
|
||||
const StyledAlert = styled(Alert)`
|
||||
${({ theme }) => `
|
||||
border: 1px solid ${theme.colors.info.base};
|
||||
padding: ${theme.gridUnit * 4}px;
|
||||
margin: ${theme.gridUnit * 6}px ${theme.gridUnit * 6}px
|
||||
${theme.gridUnit * 8}px;
|
||||
.view-dataset-button {
|
||||
position: absolute;
|
||||
top: ${theme.gridUnit * 4}px;
|
||||
right: ${theme.gridUnit * 4}px;
|
||||
font-weight: ${theme.typography.weights.normal};
|
||||
|
||||
&:hover {
|
||||
color: ${theme.colors.secondary.dark3};
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export const REFRESHING = t('Refreshing columns');
|
||||
export const COLUMN_TITLE = t('Table columns');
|
||||
export const ALT_LOADING = t('Loading');
|
||||
@ -168,19 +207,57 @@ export interface IDatasetPanelProps {
|
||||
* Boolean indicating if the component is in a loading state
|
||||
*/
|
||||
loading: boolean;
|
||||
datasets?: DatasetObject[] | undefined;
|
||||
}
|
||||
|
||||
const EXISTING_DATASET_DESCRIPTION = t(
|
||||
'This table already has a dataset associated with it. You can only associate one dataset with a table.\n',
|
||||
);
|
||||
const VIEW_DATASET = t('View Dataset');
|
||||
|
||||
const renderExistingDatasetAlert = (dataset?: DatasetObject) => (
|
||||
<StyledAlert
|
||||
closable={false}
|
||||
type="info"
|
||||
showIcon
|
||||
message={t('This table already has a dataset')}
|
||||
description={
|
||||
<>
|
||||
{EXISTING_DATASET_DESCRIPTION}
|
||||
<span
|
||||
role="button"
|
||||
onClick={() => {
|
||||
window.open(
|
||||
dataset?.explore_url,
|
||||
'_blank',
|
||||
'noreferrer noopener popup=false',
|
||||
);
|
||||
}}
|
||||
tabIndex={0}
|
||||
className="view-dataset-button"
|
||||
>
|
||||
{VIEW_DATASET}
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
const DatasetPanel = ({
|
||||
tableName,
|
||||
columnList,
|
||||
loading,
|
||||
hasError,
|
||||
datasets,
|
||||
}: IDatasetPanelProps) => {
|
||||
const theme = useTheme();
|
||||
const hasColumns = columnList?.length > 0 ?? false;
|
||||
const datasetNames = datasets?.map(dataset => dataset.table_name);
|
||||
|
||||
let component;
|
||||
let loader;
|
||||
if (loading) {
|
||||
component = (
|
||||
loader = (
|
||||
<LoaderContainer>
|
||||
<StyledLoader>
|
||||
<img alt={ALT_LOADING} src={LOADING_GIF} />
|
||||
@ -188,7 +265,9 @@ const DatasetPanel = ({
|
||||
</StyledLoader>
|
||||
</LoaderContainer>
|
||||
);
|
||||
} else if (tableName && hasColumns && !hasError) {
|
||||
}
|
||||
if (!loading) {
|
||||
if (!loading && tableName && hasColumns && !hasError) {
|
||||
component = (
|
||||
<>
|
||||
<StyledTitle>{COLUMN_TITLE}</StyledTitle>
|
||||
@ -213,10 +292,16 @@ const DatasetPanel = ({
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{tableName && (
|
||||
<>
|
||||
{datasetNames?.includes(tableName) &&
|
||||
renderExistingDatasetAlert(
|
||||
datasets?.find(dataset => dataset.table_name === tableName),
|
||||
)}
|
||||
<StyledHeader
|
||||
position={
|
||||
!loading && hasColumns ? EPosition.RELATIVE : EPosition.ABSOLUTE
|
||||
@ -224,12 +309,14 @@ const DatasetPanel = ({
|
||||
title={tableName || ''}
|
||||
>
|
||||
{tableName && (
|
||||
<Icons.Table iconColor={supersetTheme.colors.grayscale.base} />
|
||||
<Icons.Table iconColor={theme.colors.grayscale.base} />
|
||||
)}
|
||||
{tableName}
|
||||
</StyledHeader>
|
||||
</>
|
||||
)}
|
||||
{component}
|
||||
{loader}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -16,6 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { DatasetObject } from 'src/views/CRUD/data/dataset/AddDataset/types';
|
||||
import { ITableColumn } from './types';
|
||||
|
||||
export const exampleColumns: ITableColumn[] = [
|
||||
@ -32,3 +33,16 @@ export const exampleColumns: ITableColumn[] = [
|
||||
type: 'DATE',
|
||||
},
|
||||
];
|
||||
|
||||
export const exampleDataset: DatasetObject[] = [
|
||||
{
|
||||
db: {
|
||||
id: 1,
|
||||
database_name: 'test_database',
|
||||
owners: [1],
|
||||
},
|
||||
schema: 'test_schema',
|
||||
dataset_name: 'example_dataset',
|
||||
table_name: 'example_table',
|
||||
},
|
||||
];
|
||||
|
@ -18,6 +18,7 @@
|
||||
*/
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { SupersetClient } from '@superset-ui/core';
|
||||
import { DatasetObject } from 'src/views/CRUD/data/dataset/AddDataset/types';
|
||||
import DatasetPanel from './DatasetPanel';
|
||||
import { ITableColumn, IDatabaseTable, isIDatabaseTable } from './types';
|
||||
|
||||
@ -53,6 +54,7 @@ export interface IDatasetPanelWrapperProps {
|
||||
*/
|
||||
schema?: string | null;
|
||||
setHasColumns?: Function;
|
||||
datasets?: DatasetObject[] | undefined;
|
||||
}
|
||||
|
||||
const DatasetPanelWrapper = ({
|
||||
@ -60,6 +62,7 @@ const DatasetPanelWrapper = ({
|
||||
dbId,
|
||||
schema,
|
||||
setHasColumns,
|
||||
datasets,
|
||||
}: IDatasetPanelWrapperProps) => {
|
||||
const [columnList, setColumnList] = useState<ITableColumn[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@ -110,7 +113,7 @@ const DatasetPanelWrapper = ({
|
||||
if (tableName && schema && dbId) {
|
||||
getTableMetadata({ tableName, dbId, schema });
|
||||
}
|
||||
// getTableMetadata is a const and should not be independency array
|
||||
// getTableMetadata is a const and should not be in dependency array
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [tableName, dbId, schema]);
|
||||
|
||||
@ -120,6 +123,7 @@ const DatasetPanelWrapper = ({
|
||||
hasError={hasError}
|
||||
loading={loading}
|
||||
tableName={tableName}
|
||||
datasets={datasets}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -40,7 +40,7 @@ const mockPropsWithDataset = {
|
||||
};
|
||||
|
||||
describe('Footer', () => {
|
||||
it('renders a Footer with a cancel button and a disabled create button', () => {
|
||||
test('renders a Footer with a cancel button and a disabled create button', () => {
|
||||
render(<Footer {...mockedProps} />, { useRedux: true });
|
||||
|
||||
const saveButton = screen.getByRole('button', {
|
||||
@ -55,7 +55,7 @@ describe('Footer', () => {
|
||||
expect(createButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('renders a Create Dataset button when a table is selected', () => {
|
||||
test('renders a Create Dataset button when a table is selected', () => {
|
||||
render(<Footer {...mockPropsWithDataset} />, { useRedux: true });
|
||||
|
||||
const createButton = screen.getByRole('button', {
|
||||
@ -64,4 +64,16 @@ describe('Footer', () => {
|
||||
|
||||
expect(createButton).toBeEnabled();
|
||||
});
|
||||
|
||||
test('create button becomes disabled when table already has a dataset', () => {
|
||||
render(<Footer datasets={['real_info']} {...mockPropsWithDataset} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
const createButton = screen.getByRole('button', {
|
||||
name: /Create/i,
|
||||
});
|
||||
|
||||
expect(createButton).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
@ -37,6 +37,7 @@ interface FooterProps {
|
||||
datasetObject?: Partial<DatasetObject> | null;
|
||||
onDatasetAdd?: (dataset: DatasetObject) => void;
|
||||
hasColumns?: boolean;
|
||||
datasets?: (string | null | undefined)[] | undefined;
|
||||
}
|
||||
|
||||
const INPUT_FIELDS = ['db', 'schema', 'table_name'];
|
||||
@ -52,6 +53,7 @@ function Footer({
|
||||
datasetObject,
|
||||
addDangerToast,
|
||||
hasColumns = false,
|
||||
datasets,
|
||||
}: FooterProps) {
|
||||
const { createResource } = useSingleViewResource<Partial<DatasetObject>>(
|
||||
'dataset',
|
||||
@ -108,16 +110,22 @@ function Footer({
|
||||
}
|
||||
};
|
||||
|
||||
const CREATE_DATASET_TEXT = t('Create Dataset');
|
||||
const disabledCheck =
|
||||
!datasetObject?.table_name ||
|
||||
!hasColumns ||
|
||||
datasets?.includes(datasetObject?.table_name);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={cancelButtonOnClick}>Cancel</Button>
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
disabled={!datasetObject?.table_name || !hasColumns}
|
||||
disabled={disabledCheck}
|
||||
tooltip={!datasetObject?.table_name ? tooltipText : undefined}
|
||||
onClick={onSave}
|
||||
>
|
||||
{t('Create Dataset')}
|
||||
{CREATE_DATASET_TEXT}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
|
@ -209,6 +209,7 @@ test('searches for a table name', async () => {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
// Click 'test-postgres' database to access schemas
|
||||
const databaseSelect = screen.getByRole('combobox', {
|
||||
name: /select database or type database name/i,
|
||||
});
|
||||
@ -221,6 +222,7 @@ test('searches for a table name', async () => {
|
||||
|
||||
await waitFor(() => expect(schemaSelect).toBeEnabled());
|
||||
|
||||
// Click 'public' schema to access tables
|
||||
userEvent.click(schemaSelect);
|
||||
userEvent.click(screen.getAllByText('public')[1]);
|
||||
|
||||
@ -238,3 +240,46 @@ test('searches for a table name', async () => {
|
||||
expect(screen.queryByText('Sheet3')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('renders a warning icon when a table name has a pre-existing dataset', async () => {
|
||||
render(
|
||||
<LeftPanel
|
||||
setDataset={mockFun}
|
||||
schema="schema_a"
|
||||
dbId={1}
|
||||
datasets={['Sheet2']}
|
||||
/>,
|
||||
{
|
||||
useRedux: true,
|
||||
},
|
||||
);
|
||||
|
||||
// Click 'test-postgres' database to access schemas
|
||||
const databaseSelect = screen.getByRole('combobox', {
|
||||
name: /select database or type database name/i,
|
||||
});
|
||||
userEvent.click(databaseSelect);
|
||||
userEvent.click(await screen.findByText('test-postgres'));
|
||||
|
||||
const schemaSelect = screen.getByRole('combobox', {
|
||||
name: /select schema or type schema name/i,
|
||||
});
|
||||
|
||||
await waitFor(() => expect(schemaSelect).toBeEnabled());
|
||||
|
||||
// Warning icon should not show yet
|
||||
expect(
|
||||
screen.queryByRole('img', { name: 'warning' }),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
// Click 'public' schema to access tables
|
||||
userEvent.click(schemaSelect);
|
||||
userEvent.click(screen.getAllByText('public')[1]);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Sheet2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Sheet2 should now show the warning icon
|
||||
expect(screen.getByRole('img', { name: 'warning' })).toBeVisible();
|
||||
});
|
||||
|
@ -17,7 +17,14 @@
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useEffect, useState, SetStateAction, Dispatch } from 'react';
|
||||
import { SupersetClient, t, styled } from '@superset-ui/core';
|
||||
import {
|
||||
SupersetClient,
|
||||
t,
|
||||
styled,
|
||||
css,
|
||||
useTheme,
|
||||
logging,
|
||||
} from '@superset-ui/core';
|
||||
import { Input } from 'src/components/Input';
|
||||
import { Form } from 'src/components/Form';
|
||||
import Icons from 'src/components/Icons';
|
||||
@ -36,6 +43,7 @@ interface LeftPanelProps {
|
||||
setDataset: Dispatch<SetStateAction<object>>;
|
||||
schema?: string | null | undefined;
|
||||
dbId?: number;
|
||||
datasets?: (string | null | undefined)[] | undefined;
|
||||
}
|
||||
|
||||
const SearchIcon = styled(Icons.Search)`
|
||||
@ -78,6 +86,7 @@ const LeftPanelStyle = styled.div`
|
||||
top: ${theme.gridUnit * 92.25}px;
|
||||
left: ${theme.gridUnit * 3.25}px;
|
||||
right: 0;
|
||||
|
||||
.options {
|
||||
cursor: pointer;
|
||||
padding: ${theme.gridUnit * 1.75}px;
|
||||
@ -86,6 +95,7 @@ const LeftPanelStyle = styled.div`
|
||||
background-color: ${theme.colors.grayscale.light4}
|
||||
}
|
||||
}
|
||||
|
||||
.options-highlighted {
|
||||
cursor: pointer;
|
||||
padding: ${theme.gridUnit * 1.75}px;
|
||||
@ -93,6 +103,12 @@ const LeftPanelStyle = styled.div`
|
||||
background-color: ${theme.colors.primary.dark1};
|
||||
color: ${theme.colors.grayscale.light5};
|
||||
}
|
||||
|
||||
.options, .options-highlighted {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
form > span[aria-label="refresh"] {
|
||||
position: absolute;
|
||||
@ -124,7 +140,10 @@ export default function LeftPanel({
|
||||
setDataset,
|
||||
schema,
|
||||
dbId,
|
||||
datasets,
|
||||
}: LeftPanelProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
const [tableOptions, setTableOptions] = useState<Array<TableOption>>([]);
|
||||
const [resetTables, setResetTables] = useState(false);
|
||||
const [loadTables, setLoadTables] = useState(false);
|
||||
@ -166,9 +185,9 @@ export default function LeftPanel({
|
||||
setResetTables(false);
|
||||
setRefresh(false);
|
||||
})
|
||||
.catch(e => {
|
||||
console.log('error', e);
|
||||
});
|
||||
.catch(error =>
|
||||
logging.error('There was an error fetching tables', error),
|
||||
);
|
||||
};
|
||||
|
||||
const setSchema = (schema: string) => {
|
||||
@ -212,21 +231,32 @@ export default function LeftPanel({
|
||||
</div>
|
||||
);
|
||||
|
||||
const SELECT_DATABASE_AND_SCHEMA_TEXT = t('Select database & schema');
|
||||
const TABLE_LOADING_TEXT = t('Table loading');
|
||||
const NO_TABLES_FOUND_TITLE = t('No database tables found');
|
||||
const NO_TABLES_FOUND_DESCRIPTION = t('Try selecting a different schema');
|
||||
const SELECT_DATABASE_TABLE_TEXT = t('Select database table');
|
||||
const REFRESH_TABLE_LIST_TOOLTIP = t('Refresh table list');
|
||||
const REFRESH_TABLES_TEXT = t('Refresh tables');
|
||||
const SEARCH_TABLES_PLACEHOLDER_TEXT = t('Search tables');
|
||||
|
||||
return (
|
||||
<LeftPanelStyle>
|
||||
<p className="section-title db-schema">Select database & schema</p>
|
||||
<p className="section-title db-schema">
|
||||
{SELECT_DATABASE_AND_SCHEMA_TEXT}
|
||||
</p>
|
||||
<DatabaseSelector
|
||||
handleError={addDangerToast}
|
||||
onDbChange={setDatabase}
|
||||
onSchemaChange={setSchema}
|
||||
/>
|
||||
{loadTables && !refresh && Loader('Table loading')}
|
||||
{loadTables && !refresh && Loader(TABLE_LOADING_TEXT)}
|
||||
{schema && !loadTables && !tableOptions.length && !searchVal && (
|
||||
<div className="emptystate">
|
||||
<EmptyStateMedium
|
||||
image="empty-table.svg"
|
||||
title={t('No database tables found')}
|
||||
description={t('Try selecting a different schema')}
|
||||
title={NO_TABLES_FOUND_TITLE}
|
||||
description={NO_TABLES_FOUND_DESCRIPTION}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -234,15 +264,15 @@ export default function LeftPanel({
|
||||
{schema && (tableOptions.length > 0 || searchVal.length > 0) && (
|
||||
<>
|
||||
<Form>
|
||||
<p className="table-title">Select database table</p>
|
||||
<p className="table-title">{SELECT_DATABASE_TABLE_TEXT}</p>
|
||||
<RefreshLabel
|
||||
onClick={() => {
|
||||
setLoadTables(true);
|
||||
setRefresh(true);
|
||||
}}
|
||||
tooltipContent={t('Refresh table list')}
|
||||
tooltipContent={REFRESH_TABLE_LIST_TOOLTIP}
|
||||
/>
|
||||
{refresh && Loader('Refresh tables')}
|
||||
{refresh && Loader(REFRESH_TABLES_TEXT)}
|
||||
{!refresh && (
|
||||
<Input
|
||||
value={searchVal}
|
||||
@ -251,7 +281,7 @@ export default function LeftPanel({
|
||||
setSearchVal(evt.target.value);
|
||||
}}
|
||||
className="table-form"
|
||||
placeholder={t('Search tables')}
|
||||
placeholder={SEARCH_TABLES_PLACEHOLDER_TEXT}
|
||||
allowClear
|
||||
/>
|
||||
)}
|
||||
@ -269,6 +299,19 @@ export default function LeftPanel({
|
||||
onClick={() => setTable(option.value, i)}
|
||||
>
|
||||
{option.label}
|
||||
{datasets?.includes(option.value) && (
|
||||
<Icons.Warning
|
||||
iconColor={
|
||||
selectedTable === i
|
||||
? theme.colors.grayscale.light5
|
||||
: theme.colors.info.base
|
||||
}
|
||||
iconSize="m"
|
||||
css={css`
|
||||
margin-right: ${theme.gridUnit * 6}px;
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
@ -16,7 +16,10 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useReducer, Reducer, useState } from 'react';
|
||||
import React, { useReducer, Reducer, useEffect, useState } from 'react';
|
||||
import { logging } from '@superset-ui/core';
|
||||
import { UseGetDatasetsList } from 'src/views/CRUD/data/hooks';
|
||||
import rison from 'rison';
|
||||
import Header from './Header';
|
||||
import DatasetPanel from './DatasetPanel';
|
||||
import LeftPanel from './LeftPanel';
|
||||
@ -73,6 +76,36 @@ export default function AddDataset() {
|
||||
Reducer<Partial<DatasetObject> | null, DSReducerActionType>
|
||||
>(datasetReducer, null);
|
||||
const [hasColumns, setHasColumns] = useState(false);
|
||||
const [datasets, setDatasets] = useState<DatasetObject[]>([]);
|
||||
const datasetNames = datasets.map(dataset => dataset.table_name);
|
||||
const encodedSchema = dataset?.schema
|
||||
? encodeURIComponent(dataset?.schema)
|
||||
: undefined;
|
||||
|
||||
const queryParams = dataset?.schema
|
||||
? rison.encode_uri({
|
||||
filters: [
|
||||
{ col: 'schema', opr: 'eq', value: encodedSchema },
|
||||
{ col: 'sql', opr: 'dataset_is_null_or_empty', value: '!t' },
|
||||
],
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const getDatasetsList = async () => {
|
||||
await UseGetDatasetsList(queryParams)
|
||||
.then(json => {
|
||||
setDatasets(json?.result);
|
||||
})
|
||||
.catch(error =>
|
||||
logging.error('There was an error fetching dataset', error),
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (dataset?.schema) {
|
||||
getDatasetsList();
|
||||
}
|
||||
}, [dataset?.schema]);
|
||||
|
||||
const HeaderComponent = () => (
|
||||
<Header setDataset={setDataset} title={dataset?.table_name} />
|
||||
@ -83,6 +116,7 @@ export default function AddDataset() {
|
||||
setDataset={setDataset}
|
||||
schema={dataset?.schema}
|
||||
dbId={dataset?.db?.id}
|
||||
datasets={datasetNames}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -92,11 +126,17 @@ export default function AddDataset() {
|
||||
dbId={dataset?.db?.id}
|
||||
schema={dataset?.schema}
|
||||
setHasColumns={setHasColumns}
|
||||
datasets={datasets}
|
||||
/>
|
||||
);
|
||||
|
||||
const FooterComponent = () => (
|
||||
<Footer url={prevUrl} datasetObject={dataset} hasColumns={hasColumns} />
|
||||
<Footer
|
||||
url={prevUrl}
|
||||
datasetObject={dataset}
|
||||
hasColumns={hasColumns}
|
||||
datasets={datasetNames}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -32,6 +32,7 @@ export interface DatasetObject {
|
||||
schema?: string | null;
|
||||
dataset_name: string;
|
||||
table_name?: string | null;
|
||||
explore_url?: string;
|
||||
}
|
||||
|
||||
export interface DatasetReducerPayloadType {
|
||||
|
@ -82,6 +82,7 @@ export const StyledLayoutLeftPanel = styled.div`
|
||||
|
||||
export const StyledLayoutDatasetPanel = styled.div`
|
||||
width: 100%;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export const StyledLayoutRightPanel = styled.div`
|
||||
|
@ -17,6 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { useState, useEffect } from 'react';
|
||||
import { SupersetClient, logging } from '@superset-ui/core';
|
||||
|
||||
type BaseQueryObject = {
|
||||
id: number;
|
||||
@ -73,3 +74,12 @@ export function useQueryPreviewState<D extends BaseQueryObject = any>({
|
||||
disableNext,
|
||||
};
|
||||
}
|
||||
|
||||
export const UseGetDatasetsList = (queryParams: string | undefined) =>
|
||||
SupersetClient.get({
|
||||
endpoint: `/api/v1/dataset/?q=${queryParams}`,
|
||||
})
|
||||
.then(({ json }) => json)
|
||||
.catch(error =>
|
||||
logging.error('There was an error fetching dataset', error),
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user