Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Flow for tables that already have a dataset #22136

Merged
merged 16 commits into from
Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to coment on line 20, we shuld repelase use of supertThem with:
const StyledAlert = styled(Alert)( ({ theme }) =>

and replace all the supersetTheme with theme
for example:
`border: 1px solid ${theme.colors.info.base};

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FIxed in this commit.

${({ 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I kind of want this to have a better name, maybe existingDatasets? or something along those lines.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going off of what @eschutho suggested in this review comment. I'm not really settled on the best name here but I'd be fine changing it, would like to hear what Elizabeth thinks of existingDatasets as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense!

Does this need to be a list or can it be boolean? From my, limited, understanding of this feature you're seeing if there already is a dataset linked, so we don't need to be passing in a list of datasets, we could just check to see if there is an associated one.

Copy link
Member Author

@lyndsiWilliams lyndsiWilliams Nov 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need the whole object so we can cross-reference the list of table names with the table names in the list of datasets here:

{datasetNames?.includes(tableName) &&
renderExistingDatasetAlert(
datasets?.find(dataset => dataset.table_name === tableName),
)}

And when it's passed into renderExistingDatasetAlert() we need to be able to pull out the explore_url from the dataset object here:

onClick={() => {
window.open(
dataset?.explore_url,
'_blank',
'noreferrer noopener popup=false',
);
}}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it! Makes sense

}

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',
},
];
Loading