mirror of
https://github.com/apache/superset.git
synced 2024-09-12 08:39:45 -04:00
fix(explore): timestamp format when copy datatable to clipboard (#17166)
* fix(explore): timestamp format when copy datatable to clipboard * Fix test * Fix test * Use translation for aria label * Memoize stringified cells * Fix test
This commit is contained in:
parent
e5a03423f9
commit
860e481a97
@ -27,10 +27,7 @@ import {
|
||||
SLOW_DEBOUNCE,
|
||||
} from 'src/constants';
|
||||
import Button from 'src/components/Button';
|
||||
import {
|
||||
applyFormattingToTabularData,
|
||||
prepareCopyToClipboardTabularData,
|
||||
} from 'src/utils/common';
|
||||
import { prepareCopyToClipboardTabularData } from 'src/utils/common';
|
||||
import CopyToClipboard from 'src/components/CopyToClipboard';
|
||||
import RowCountLabel from 'src/explore/components/RowCountLabel';
|
||||
|
||||
@ -48,7 +45,7 @@ export const CopyButton = styled(Button)`
|
||||
`;
|
||||
|
||||
const CopyNode = (
|
||||
<CopyButton buttonSize="xsmall">
|
||||
<CopyButton buttonSize="xsmall" aria-label={t('Copy')}>
|
||||
<i className="fa fa-clipboard" />
|
||||
</CopyButton>
|
||||
);
|
||||
@ -103,18 +100,26 @@ export const RowCount = ({
|
||||
export const useFilteredTableData = (
|
||||
filterText: string,
|
||||
data?: Record<string, any>[],
|
||||
) =>
|
||||
useMemo(() => {
|
||||
) => {
|
||||
const rowsAsStrings = useMemo(
|
||||
() =>
|
||||
data?.map((row: Record<string, any>) =>
|
||||
Object.values(row).map(value => value?.toString().toLowerCase()),
|
||||
) ?? [],
|
||||
[data],
|
||||
);
|
||||
|
||||
return useMemo(() => {
|
||||
if (!data?.length) {
|
||||
return [];
|
||||
}
|
||||
const formattedData = applyFormattingToTabularData(data);
|
||||
return formattedData.filter((row: Record<string, any>) =>
|
||||
Object.values(row).some(value =>
|
||||
value?.toString().toLowerCase().includes(filterText.toLowerCase()),
|
||||
return data.filter((_, index: number) =>
|
||||
rowsAsStrings[index].some(value =>
|
||||
value?.includes(filterText.toLowerCase()),
|
||||
),
|
||||
);
|
||||
}, [data, filterText]);
|
||||
}, [data, filterText, rowsAsStrings]);
|
||||
};
|
||||
|
||||
export const useTableColumns = (
|
||||
colnames?: string[],
|
||||
|
@ -17,17 +17,13 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
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 { DataTablesPane } from '.';
|
||||
|
||||
fetchMock.post(
|
||||
'http://api/v1/chart/data?form_data=%7B%22slice_id%22%3A456%7D',
|
||||
{ body: {} },
|
||||
);
|
||||
|
||||
const createProps = () => ({
|
||||
queryFormData: {
|
||||
viz_type: 'heatmap',
|
||||
@ -65,10 +61,6 @@ const createProps = () => ({
|
||||
],
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
fetchMock.done();
|
||||
});
|
||||
|
||||
test('Rendering DataTablesPane correctly', () => {
|
||||
const props = createProps();
|
||||
render(<DataTablesPane {...props} />, { useRedux: true });
|
||||
@ -108,3 +100,38 @@ test('Should show tabs: View samples', async () => {
|
||||
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' }] }],
|
||||
},
|
||||
);
|
||||
const copyToClipboardSpy = jest.spyOn(copyUtils, 'default');
|
||||
const props = createProps();
|
||||
render(
|
||||
<DataTablesPane
|
||||
{...{
|
||||
...props,
|
||||
chartStatus: 'success',
|
||||
queriesResponse: [
|
||||
{
|
||||
colnames: ['__timestamp', 'genre'],
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
useRedux: true,
|
||||
},
|
||||
);
|
||||
userEvent.click(await screen.findByText('Data'));
|
||||
expect(await screen.findByText('1 rows retrieved')).toBeVisible();
|
||||
|
||||
userEvent.click(screen.getByRole('button', { name: 'Copy' }));
|
||||
expect(copyToClipboardSpy).toHaveBeenCalledWith(
|
||||
'2009-01-01 00:00:00\tAction\n',
|
||||
);
|
||||
fetchMock.done();
|
||||
});
|
||||
|
@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { JsonObject, styled, t } from '@superset-ui/core';
|
||||
import Collapse from 'src/components/Collapse';
|
||||
import Tabs from 'src/components/Tabs';
|
||||
@ -35,6 +35,7 @@ import {
|
||||
useFilteredTableData,
|
||||
useTableColumns,
|
||||
} from 'src/explore/components/DataTableControl';
|
||||
import { applyFormattingToTabularData } from 'src/utils/common';
|
||||
|
||||
const RESULT_TYPES = {
|
||||
results: 'results' as const,
|
||||
@ -144,6 +145,18 @@ export const DataTablesPane = ({
|
||||
getFromLocalStorage(STORAGE_KEYS.isOpen, false),
|
||||
);
|
||||
|
||||
const formattedData = useMemo(
|
||||
() => ({
|
||||
[RESULT_TYPES.results]: applyFormattingToTabularData(
|
||||
data[RESULT_TYPES.results],
|
||||
),
|
||||
[RESULT_TYPES.samples]: applyFormattingToTabularData(
|
||||
data[RESULT_TYPES.samples],
|
||||
),
|
||||
}),
|
||||
[data],
|
||||
);
|
||||
|
||||
const getData = useCallback(
|
||||
(resultType: string) => {
|
||||
setIsLoading(prevIsLoading => ({
|
||||
@ -279,11 +292,11 @@ export const DataTablesPane = ({
|
||||
const filteredData = {
|
||||
[RESULT_TYPES.results]: useFilteredTableData(
|
||||
filterText,
|
||||
data[RESULT_TYPES.results],
|
||||
formattedData[RESULT_TYPES.results],
|
||||
),
|
||||
[RESULT_TYPES.samples]: useFilteredTableData(
|
||||
filterText,
|
||||
data[RESULT_TYPES.samples],
|
||||
formattedData[RESULT_TYPES.samples],
|
||||
),
|
||||
};
|
||||
|
||||
@ -334,7 +347,10 @@ export const DataTablesPane = ({
|
||||
const TableControls = (
|
||||
<TableControlsWrapper>
|
||||
<RowCount data={data[activeTabKey]} loading={isLoading[activeTabKey]} />
|
||||
<CopyToClipboardButton data={data[activeTabKey]} columns={columnNames} />
|
||||
<CopyToClipboardButton
|
||||
data={formattedData[activeTabKey]}
|
||||
columns={columnNames}
|
||||
/>
|
||||
<FilterInput onChangeHandler={setFilterText} />
|
||||
</TableControlsWrapper>
|
||||
);
|
||||
|
@ -55,7 +55,6 @@ describe('utils/common', () => {
|
||||
{ column1: 'dolor', column2: 'sit', column3: 'amet' },
|
||||
];
|
||||
const column = ['column1', 'column2', 'column3'];
|
||||
console.log(prepareCopyToClipboardTabularData(array, column));
|
||||
expect(prepareCopyToClipboardTabularData(array, column)).toEqual(
|
||||
'lorem\tipsum\t\ndolor\tsit\tamet\n',
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user