Skip to content

Commit

Permalink
feat: Flow for tables that already have a dataset (#22136)
Browse files Browse the repository at this point in the history
  • Loading branch information
lyndsiWilliams committed Dec 5, 2022
1 parent 96de314 commit 04b7a26
Show file tree
Hide file tree
Showing 12 changed files with 366 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 });
Expand All @@ -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"
Expand All @@ -83,7 +83,7 @@ describe('DatasetPanel', () => {
expect(noColumnsDescription).toBeVisible();
});

it('renders a loading screen', () => {
test('renders a loading screen', () => {
render(
<DatasetPanel
tableName="Name"
Expand All @@ -99,7 +99,7 @@ describe('DatasetPanel', () => {
expect(blankDatasetTitle).toBeVisible();
});

it('renders an error screen', () => {
test('renders an error screen', () => {
render(
<DatasetPanel
tableName="Name"
Expand All @@ -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
Expand All @@ -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();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
Expand All @@ -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)`
Expand All @@ -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');
Expand Down Expand Up @@ -168,68 +207,116 @@ 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} />
<div>{REFRESHING}</div>
</StyledLoader>
</LoaderContainer>
);
} else if (tableName && hasColumns && !hasError) {
component = (
<>
<StyledTitle>{COLUMN_TITLE}</StyledTitle>
<TableContainer>
<StyledTable
loading={loading}
size={TableSize.SMALL}
columns={tableColumnDefinition}
data={columnList}
pageSizeOptions={pageSizeOptions}
defaultPageSize={10}
/>
</TableContainer>
</>
);
} else {
component = (
<MessageContent
hasColumns={hasColumns}
hasError={hasError}
tableName={tableName}
/>
);
}
if (!loading) {
if (!loading && tableName && hasColumns && !hasError) {
component = (
<>
<StyledTitle>{COLUMN_TITLE}</StyledTitle>
<TableContainer>
<StyledTable
loading={loading}
size={TableSize.SMALL}
columns={tableColumnDefinition}
data={columnList}
pageSizeOptions={pageSizeOptions}
defaultPageSize={10}
/>
</TableContainer>
</>
);
} else {
component = (
<MessageContent
hasColumns={hasColumns}
hasError={hasError}
tableName={tableName}
/>
);
}
}

return (
<>
{tableName && (
<StyledHeader
position={
!loading && hasColumns ? EPosition.RELATIVE : EPosition.ABSOLUTE
}
title={tableName || ''}
>
{tableName && (
<Icons.Table iconColor={supersetTheme.colors.grayscale.base} />
)}
{tableName}
</StyledHeader>
<>
{datasetNames?.includes(tableName) &&
renderExistingDatasetAlert(
datasets?.find(dataset => dataset.table_name === tableName),
)}
<StyledHeader
position={
!loading && hasColumns ? EPosition.RELATIVE : EPosition.ABSOLUTE
}
title={tableName || ''}
>
{tableName && (
<Icons.Table iconColor={theme.colors.grayscale.base} />
)}
{tableName}
</StyledHeader>
</>
)}
{component}
{loader}
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [
Expand All @@ -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',
},
];

0 comments on commit 04b7a26

Please sign in to comment.