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(explore): Implement data panel redesign #19751

Merged
merged 5 commits into from
Apr 19, 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 @@ -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();
});
});
Loading