Skip to content

Commit

Permalink
feat(explore): Implement data panel redesign (apache#19751)
Browse files Browse the repository at this point in the history
* feat(explore): Redesign of data panel

* Auto calculate chart panel height and width

* Add tests

* Fix e2e tests

* Increase collapsed data panel height
  • Loading branch information
kgabryje authored and philipher29 committed Jun 9, 2022
1 parent 5f1dc9d commit 0e6b85f
Show file tree
Hide file tree
Showing 6 changed files with 494 additions and 359 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,12 @@ describe('Test datatable', () => {
cy.visitChartByName('Daily Totals');
});
it('Data Pane opens and loads results', () => {
cy.get('[data-test="data-tab"]').click();
cy.contains('Results').click();
cy.get('[data-test="row-count-label"]').contains('26 rows retrieved');
cy.contains('View results');
cy.get('.ant-empty-description').should('not.exist');
});
it('Datapane loads view samples', () => {
cy.get('[data-test="data-tab"]').click();
cy.contains('View samples').click();
cy.contains('Samples').click();
cy.get('[data-test="row-count-label"]').contains('1k rows retrieved');
cy.get('.ant-empty-description').should('not.exist');
});
Expand Down
45 changes: 30 additions & 15 deletions superset-frontend/src/explore/components/DataTableControl/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,41 +67,56 @@ export const CopyButton = styled(Button)`
}
`;

const CopyNode = (
<CopyButton buttonSize="xsmall" aria-label={t('Copy')}>
<i className="fa fa-clipboard" />
</CopyButton>
);

export const CopyToClipboardButton = ({
data,
columns,
}: {
data?: Record<string, any>;
columns?: string[];
}) => (
<CopyToClipboard
text={
data && columns ? prepareCopyToClipboardTabularData(data, columns) : ''
}
wrapped={false}
copyNode={CopyNode}
/>
);
}) => {
const theme = useTheme();
return (
<CopyToClipboard
text={
data && columns ? prepareCopyToClipboardTabularData(data, columns) : ''
}
wrapped={false}
copyNode={
<Icons.CopyOutlined
iconColor={theme.colors.grayscale.base}
iconSize="l"
aria-label={t('Copy')}
role="button"
css={css`
&.anticon > * {
line-height: 0;
}
`}
/>
}
/>
);
};

export const FilterInput = ({
onChangeHandler,
}: {
onChangeHandler(filterText: string): void;
}) => {
const theme = useTheme();
const debouncedChangeHandler = debounce(onChangeHandler, SLOW_DEBOUNCE);
return (
<Input
prefix={<Icons.Search iconColor={theme.colors.grayscale.base} />}
placeholder={t('Search')}
onChange={(event: any) => {
const filterText = event.target.value;
debouncedChangeHandler(filterText);
}}
css={css`
width: 200px;
margin-right: ${theme.gridUnit * 2}px;
`}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import React from 'react';
import userEvent from '@testing-library/user-event';
import fetchMock from 'fetch-mock';
import * as copyUtils from 'src/utils/copy';
import { render, screen } from 'spec/helpers/testing-library';
import {
render,
screen,
waitForElementToBeRemoved,
} from 'spec/helpers/testing-library';
import { DataTablesPane } from '.';

const createProps = () => ({
Expand Down Expand Up @@ -50,7 +54,6 @@ const createProps = () => ({
sort_y_axis: 'alpha_asc',
extra_form_data: {},
},
tableSectionHeight: 156.9,
chartStatus: 'rendered',
onCollapseChange: jest.fn(),
queriesResponse: [
Expand All @@ -60,91 +63,162 @@ const createProps = () => ({
],
});

test('Rendering DataTablesPane correctly', () => {
const props = createProps();
render(<DataTablesPane {...props} />, { useRedux: true });
expect(screen.getByTestId('some-purposeful-instance')).toBeVisible();
expect(screen.getByRole('tablist')).toBeVisible();
expect(screen.getByRole('tab', { name: 'right Data' })).toBeVisible();
expect(screen.getByRole('img', { name: 'right' })).toBeVisible();
});
describe('DataTablesPane', () => {
// Collapsed/expanded state depends on local storage
// We need to clear it manually - otherwise initial state would depend on the order of tests
beforeEach(() => {
localStorage.clear();
});

test('Should show tabs', async () => {
const props = createProps();
render(<DataTablesPane {...props} />, { useRedux: true });
expect(screen.queryByText('View results')).not.toBeInTheDocument();
expect(screen.queryByText('View samples')).not.toBeInTheDocument();
userEvent.click(await screen.findByText('Data'));
expect(await screen.findByText('View results')).toBeVisible();
expect(screen.getByText('View samples')).toBeVisible();
});
afterAll(() => {
localStorage.clear();
});

test('Should show tabs: View results', async () => {
const props = createProps();
render(<DataTablesPane {...props} />, {
useRedux: true,
test('Rendering DataTablesPane correctly', () => {
const props = createProps();
render(<DataTablesPane {...props} />, { useRedux: true });
expect(screen.getByText('Results')).toBeVisible();
expect(screen.getByText('Samples')).toBeVisible();
expect(screen.getByLabelText('Expand data panel')).toBeVisible();
});
userEvent.click(await screen.findByText('Data'));
userEvent.click(await screen.findByText('View results'));
expect(screen.getByText('0 rows retrieved')).toBeVisible();
});

test('Should show tabs: View samples', async () => {
const props = createProps();
render(<DataTablesPane {...props} />, {
useRedux: true,
test('Collapse/Expand buttons', async () => {
const props = createProps();
render(<DataTablesPane {...props} />, {
useRedux: true,
});
expect(
screen.queryByLabelText('Collapse data panel'),
).not.toBeInTheDocument();
userEvent.click(screen.getByLabelText('Expand data panel'));
expect(await screen.findByLabelText('Collapse data panel')).toBeVisible();
expect(
screen.queryByLabelText('Expand data panel'),
).not.toBeInTheDocument();
});
userEvent.click(await screen.findByText('Data'));
expect(screen.queryByText('0 rows retrieved')).not.toBeInTheDocument();
userEvent.click(await screen.findByText('View samples'));
expect(await screen.findByText('0 rows retrieved')).toBeVisible();
});

test('Should copy data table content correctly', async () => {
fetchMock.post(
'glob:*/api/v1/chart/data?form_data=%7B%22slice_id%22%3A456%7D',
{
result: [
{
data: [{ __timestamp: 1230768000000, genre: 'Action' }],
colnames: ['__timestamp', 'genre'],
coltypes: [2, 1],
test('Should show tabs: View results', async () => {
const props = createProps();
render(<DataTablesPane {...props} />, {
useRedux: true,
});
userEvent.click(screen.getByText('Results'));
expect(await screen.findByText('0 rows retrieved')).toBeVisible();
expect(await screen.findByLabelText('Collapse data panel')).toBeVisible();
localStorage.clear();
});

test('Should show tabs: View samples', async () => {
const props = createProps();
render(<DataTablesPane {...props} />, {
useRedux: true,
});
userEvent.click(screen.getByText('Samples'));
expect(await screen.findByText('0 rows retrieved')).toBeVisible();
expect(await screen.findByLabelText('Collapse data panel')).toBeVisible();
});

test('Should copy data table content correctly', async () => {
fetchMock.post(
'glob:*/api/v1/chart/data?form_data=%7B%22slice_id%22%3A456%7D',
{
result: [
{
data: [{ __timestamp: 1230768000000, genre: 'Action' }],
colnames: ['__timestamp', 'genre'],
coltypes: [2, 1],
},
],
},
);
const copyToClipboardSpy = jest.spyOn(copyUtils, 'default');
const props = createProps();
render(
<DataTablesPane
{...{
...props,
chartStatus: 'success',
queriesResponse: [
{
colnames: ['__timestamp', 'genre'],
coltypes: [2, 1],
},
],
}}
/>,
{
useRedux: true,
initialState: {
explore: {
timeFormattedColumns: {
'34__table': ['__timestamp'],
},
},
},
],
},
);
const copyToClipboardSpy = jest.spyOn(copyUtils, 'default');
const props = createProps();
render(
<DataTablesPane
{...{
...props,
chartStatus: 'success',
queriesResponse: [
},
);
userEvent.click(screen.getByText('Results'));
expect(await screen.findByText('1 rows retrieved')).toBeVisible();

userEvent.click(screen.getByLabelText('Copy'));
expect(copyToClipboardSpy).toHaveBeenCalledWith(
'2009-01-01 00:00:00\tAction\n',
);
copyToClipboardSpy.mockRestore();
fetchMock.restore();
});

test('Search table', async () => {
fetchMock.post(
'glob:*/api/v1/chart/data?form_data=%7B%22slice_id%22%3A456%7D',
{
result: [
{
data: [
{ __timestamp: 1230768000000, genre: 'Action' },
{ __timestamp: 1230768000010, genre: 'Horror' },
],
colnames: ['__timestamp', 'genre'],
coltypes: [2, 1],
},
],
}}
/>,
{
useRedux: true,
initialState: {
explore: {
timeFormattedColumns: {
'34__table': ['__timestamp'],
},
);
const props = createProps();
render(
<DataTablesPane
{...{
...props,
chartStatus: 'success',
queriesResponse: [
{
colnames: ['__timestamp', 'genre'],
coltypes: [2, 1],
},
],
}}
/>,
{
useRedux: true,
initialState: {
explore: {
timeFormattedColumns: {
'34__table': ['__timestamp'],
},
},
},
},
},
);
userEvent.click(await screen.findByText('Data'));
expect(await screen.findByText('1 rows retrieved')).toBeVisible();
);
userEvent.click(screen.getByText('Results'));
expect(await screen.findByText('2 rows retrieved')).toBeVisible();
expect(screen.getByText('Action')).toBeVisible();
expect(screen.getByText('Horror')).toBeVisible();

userEvent.click(screen.getByRole('button', { name: 'Copy' }));
expect(copyToClipboardSpy).toHaveBeenCalledWith(
'2009-01-01 00:00:00\tAction\n',
);
fetchMock.done();
userEvent.type(screen.getByPlaceholderText('Search'), 'hor');

await waitForElementToBeRemoved(() => screen.queryByText('Action'));
expect(screen.getByText('Horror')).toBeVisible();
expect(screen.queryByText('Action')).not.toBeInTheDocument();
fetchMock.restore();
});
});

0 comments on commit 0e6b85f

Please sign in to comment.