chore(data-table): make formatted dttm the default (#20140)

fix test
This commit is contained in:
Ville Brofeldt 2022-05-20 15:39:10 +03:00 committed by GitHub
parent d8117f7e37
commit 0bcc21bc45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 199 additions and 167 deletions

View File

@ -140,25 +140,27 @@ export function sliceUpdated(slice: Slice) {
return { type: SLICE_UPDATED, slice };
}
export const SET_TIME_FORMATTED_COLUMN = 'SET_TIME_FORMATTED_COLUMN';
export function setTimeFormattedColumn(
export const SET_ORIGINAL_FORMATTED_TIME_COLUMN =
'SET_ORIGINAL_FORMATTED_TIME_COLUMN';
export function setOriginalFormattedTimeColumn(
datasourceId: string,
columnName: string,
) {
return {
type: SET_TIME_FORMATTED_COLUMN,
type: SET_ORIGINAL_FORMATTED_TIME_COLUMN,
datasourceId,
columnName,
};
}
export const UNSET_TIME_FORMATTED_COLUMN = 'UNSET_TIME_FORMATTED_COLUMN';
export function unsetTimeFormattedColumn(
export const UNSET_ORIGINAL_FORMATTED_TIME_COLUMN =
'UNSET_ORIGINAL_FORMATTED_TIME_COLUMN';
export function unsetOriginalFormattedTimeColumn(
datasourceId: string,
columnIndex: number,
) {
return {
type: UNSET_TIME_FORMATTED_COLUMN,
type: UNSET_ORIGINAL_FORMATTED_TIME_COLUMN,
datasourceId,
columnIndex,
};
@ -187,8 +189,8 @@ export const exploreActions = {
updateChartTitle,
createNewSlice,
sliceUpdated,
setTimeFormattedColumn,
unsetTimeFormattedColumn,
setOriginalFormattedTimeColumn,
unsetOriginalFormattedTimeColumn,
setForceQuery,
};

View File

@ -35,8 +35,8 @@ import { Input } from 'src/components/Input';
import {
BOOL_FALSE_DISPLAY,
BOOL_TRUE_DISPLAY,
SLOW_DEBOUNCE,
NULL_DISPLAY,
SLOW_DEBOUNCE,
} from 'src/constants';
import { Radio } from 'src/components/Radio';
import Icons from 'src/components/Icons';
@ -46,8 +46,8 @@ import { prepareCopyToClipboardTabularData } from 'src/utils/common';
import CopyToClipboard from 'src/components/CopyToClipboard';
import RowCountLabel from 'src/explore/components/RowCountLabel';
import {
setTimeFormattedColumn,
unsetTimeFormattedColumn,
setOriginalFormattedTimeColumn,
unsetOriginalFormattedTimeColumn,
} from 'src/explore/actions/exploreActions';
export const CellNull = styled('span')`
@ -143,8 +143,8 @@ const FormatPicker = ({
}) => (
<Radio.Group value={value} onChange={onChange}>
<Space direction="vertical">
<Radio value={FormatPickerValue.Original}>{t('Original value')}</Radio>
<Radio value={FormatPickerValue.Formatted}>{t('Formatted date')}</Radio>
<Radio value={FormatPickerValue.Original}>{t('Original value')}</Radio>
</Space>
</Radio.Group>
);
@ -166,15 +166,15 @@ const FormatPickerLabel = styled.span`
const DataTableTemporalHeaderCell = ({
columnName,
datasourceId,
timeFormattedColumnIndex,
originalFormattedTimeColumnIndex,
}: {
columnName: string;
datasourceId?: string;
timeFormattedColumnIndex: number;
originalFormattedTimeColumnIndex: number;
}) => {
const theme = useTheme();
const dispatch = useDispatch();
const isColumnTimeFormatted = timeFormattedColumnIndex > -1;
const isTimeColumnOriginalFormatted = originalFormattedTimeColumnIndex > -1;
const onChange = useCallback(
e => {
@ -183,24 +183,27 @@ const DataTableTemporalHeaderCell = ({
}
if (
e.target.value === FormatPickerValue.Original &&
isColumnTimeFormatted
!isTimeColumnOriginalFormatted
) {
dispatch(
unsetTimeFormattedColumn(datasourceId, timeFormattedColumnIndex),
);
dispatch(setOriginalFormattedTimeColumn(datasourceId, columnName));
} else if (
e.target.value === FormatPickerValue.Formatted &&
!isColumnTimeFormatted
isTimeColumnOriginalFormatted
) {
dispatch(setTimeFormattedColumn(datasourceId, columnName));
dispatch(
unsetOriginalFormattedTimeColumn(
datasourceId,
originalFormattedTimeColumnIndex,
),
);
}
},
[
timeFormattedColumnIndex,
originalFormattedTimeColumnIndex,
columnName,
datasourceId,
dispatch,
isColumnTimeFormatted,
isTimeColumnOriginalFormatted,
],
);
const overlayContent = useMemo(
@ -219,14 +222,14 @@ const DataTableTemporalHeaderCell = ({
<FormatPicker
onChange={onChange}
value={
isColumnTimeFormatted
? FormatPickerValue.Formatted
: FormatPickerValue.Original
isTimeColumnOriginalFormatted
? FormatPickerValue.Original
: FormatPickerValue.Formatted
}
/>
</FormatPickerContainer>
) : null,
[datasourceId, isColumnTimeFormatted, onChange],
[datasourceId, isTimeColumnOriginalFormatted, onChange],
);
return datasourceId ? (
@ -285,7 +288,7 @@ export const useTableColumns = (
coltypes?: GenericDataType[],
data?: Record<string, any>[],
datasourceId?: string,
timeFormattedColumns: string[] = [],
originalFormattedTimeColumns: string[] = [],
moreConfigs?: { [key: string]: Partial<Column> },
) =>
useMemo(
@ -294,20 +297,25 @@ export const useTableColumns = (
? colnames
.filter((column: string) => Object.keys(data[0]).includes(column))
.map((key, index) => {
const timeFormattedColumnIndex =
coltypes?.[index] === GenericDataType.TEMPORAL
? timeFormattedColumns.indexOf(key)
const colType = coltypes?.[index];
const firstValue = data[0][key];
const originalFormattedTimeColumnIndex =
colType === GenericDataType.TEMPORAL
? originalFormattedTimeColumns.indexOf(key)
: -1;
return {
id: key,
accessor: row => row[key],
// When the key is empty, have to give a string of length greater than 0
Header:
coltypes?.[index] === GenericDataType.TEMPORAL ? (
colType === GenericDataType.TEMPORAL &&
typeof firstValue !== 'string' ? (
<DataTableTemporalHeaderCell
columnName={key}
datasourceId={datasourceId}
timeFormattedColumnIndex={timeFormattedColumnIndex}
originalFormattedTimeColumnIndex={
originalFormattedTimeColumnIndex
}
/>
) : (
key
@ -322,7 +330,11 @@ export const useTableColumns = (
if (value === null) {
return <CellNull>{NULL_DISPLAY}</CellNull>;
}
if (timeFormattedColumnIndex > -1) {
if (
colType === GenericDataType.TEMPORAL &&
originalFormattedTimeColumnIndex === -1 &&
typeof value === 'number'
) {
return timeFormatter(value);
}
return String(value);
@ -331,5 +343,12 @@ export const useTableColumns = (
} as Column;
})
: [],
[colnames, data, coltypes, datasourceId, moreConfigs, timeFormattedColumns],
[
colnames,
data,
coltypes,
datasourceId,
moreConfigs,
originalFormattedTimeColumns,
],
);

View File

@ -28,31 +28,53 @@ const asciiChars = [];
for (let i = 32; i < 127; i += 1) {
asciiChars.push(String.fromCharCode(i));
}
const asciiKey = asciiChars.join('');
const unicodeKey = '你好. 吃了吗?';
const ASCII_KEY = asciiChars.join('');
const UNICODE_KEY = '你好. 吃了吗?';
const NUMTIME_KEY = 'numtime';
const STRTIME_KEY = 'strtime';
const NUMTIME_VALUE = 1640995200000;
const NUMTIME_FORMATTED_VALUE = '2022-01-01 00:00:00';
const STRTIME_VALUE = '2022-01-01';
const data = [
{ col01: true, col02: false, [asciiKey]: asciiKey, [unicodeKey]: unicodeKey },
{ col01: true, col02: false, [asciiKey]: asciiKey, [unicodeKey]: unicodeKey },
{ col01: true, col02: false, [asciiKey]: asciiKey, [unicodeKey]: unicodeKey },
{
col01: true,
col02: false,
col03: 'secret',
[asciiKey]: asciiKey,
[unicodeKey]: unicodeKey,
},
const colnames = [
'col01',
'col02',
ASCII_KEY,
UNICODE_KEY,
NUMTIME_KEY,
STRTIME_KEY,
];
const all_columns = ['col01', 'col02', 'col03', asciiKey, unicodeKey];
const coltypes = [
GenericDataType.BOOLEAN,
GenericDataType.BOOLEAN,
GenericDataType.STRING,
GenericDataType.STRING,
GenericDataType.TEMPORAL,
GenericDataType.TEMPORAL,
];
const cellValues = {
col01: true,
col02: false,
[ASCII_KEY]: ASCII_KEY,
[UNICODE_KEY]: UNICODE_KEY,
[NUMTIME_KEY]: NUMTIME_VALUE,
[STRTIME_KEY]: STRTIME_VALUE,
};
const data = [cellValues, cellValues, cellValues, cellValues];
const expectedDisplayValues = {
col01: BOOL_TRUE_DISPLAY,
col02: BOOL_FALSE_DISPLAY,
[ASCII_KEY]: ASCII_KEY,
[UNICODE_KEY]: UNICODE_KEY,
[NUMTIME_KEY]: NUMTIME_FORMATTED_VALUE,
[STRTIME_KEY]: STRTIME_VALUE,
};
test('useTableColumns with no options', () => {
const hook = renderHook(() => useTableColumns(all_columns, coltypes, data));
const hook = renderHook(() => useTableColumns(colnames, coltypes, data));
expect(hook.result.current).toEqual([
{
Cell: expect.any(Function),
@ -68,102 +90,61 @@ test('useTableColumns with no options', () => {
},
{
Cell: expect.any(Function),
Header: asciiKey,
Header: ASCII_KEY,
accessor: expect.any(Function),
id: asciiKey,
id: ASCII_KEY,
},
{
Cell: expect.any(Function),
Header: unicodeKey,
Header: UNICODE_KEY,
accessor: expect.any(Function),
id: unicodeKey,
id: UNICODE_KEY,
},
{
Cell: expect.any(Function),
Header: expect.objectContaining({
type: expect.objectContaining({
name: 'DataTableTemporalHeaderCell',
}),
props: expect.objectContaining({
originalFormattedTimeColumnIndex: -1,
}),
}),
accessor: expect.any(Function),
id: NUMTIME_KEY,
},
{
Cell: expect.any(Function),
Header: STRTIME_KEY,
accessor: expect.any(Function),
id: STRTIME_KEY,
},
]);
hook.result.current.forEach((col: JsonObject) => {
expect(col.accessor(data[0])).toBe(data[0][col.Header]);
expect(col.accessor(data[0])).toBe(data[0][col.id]);
});
hook.result.current.forEach((col: JsonObject) => {
data.forEach(row => {
expect(col.Cell({ value: row.col01 })).toBe(BOOL_TRUE_DISPLAY);
expect(col.Cell({ value: row.col02 })).toBe(BOOL_FALSE_DISPLAY);
expect(col.Cell({ value: row[asciiKey] })).toBe(asciiKey);
expect(col.Cell({ value: row[unicodeKey] })).toBe(unicodeKey);
expect(col.Cell({ value: row[col.id] })).toBe(
expectedDisplayValues[col.id],
);
});
});
});
test('use only the first record columns', () => {
const newData = [data[3], data[0]];
const hook = renderHook(() =>
useTableColumns(all_columns, coltypes, newData),
);
expect(hook.result.current).toEqual([
{
Cell: expect.any(Function),
Header: 'col01',
accessor: expect.any(Function),
id: 'col01',
},
{
Cell: expect.any(Function),
Header: 'col02',
accessor: expect.any(Function),
id: 'col02',
},
{
Cell: expect.any(Function),
Header: 'col03',
accessor: expect.any(Function),
id: 'col03',
},
{
Cell: expect.any(Function),
Header: asciiKey,
accessor: expect.any(Function),
id: asciiKey,
},
{
Cell: expect.any(Function),
Header: unicodeKey,
accessor: expect.any(Function),
id: unicodeKey,
},
]);
hook.result.current.forEach((col: JsonObject) => {
expect(col.accessor(newData[0])).toBe(newData[0][col.Header]);
});
hook.result.current.forEach((col: JsonObject) => {
expect(col.Cell({ value: newData[0].col01 })).toBe(BOOL_TRUE_DISPLAY);
expect(col.Cell({ value: newData[0].col02 })).toBe(BOOL_FALSE_DISPLAY);
expect(col.Cell({ value: newData[0].col03 })).toBe('secret');
expect(col.Cell({ value: newData[0][asciiKey] })).toBe(asciiKey);
expect(col.Cell({ value: newData[0][unicodeKey] })).toBe(unicodeKey);
});
hook.result.current.forEach((col: JsonObject) => {
expect(col.Cell({ value: newData[1].col01 })).toBe(BOOL_TRUE_DISPLAY);
expect(col.Cell({ value: newData[1].col02 })).toBe(BOOL_FALSE_DISPLAY);
expect(col.Cell({ value: newData[1].col03 })).toBe('undefined');
expect(col.Cell({ value: newData[1][asciiKey] })).toBe(asciiKey);
expect(col.Cell({ value: newData[1][unicodeKey] })).toBe(unicodeKey);
});
});
test('useTableColumns with options', () => {
const hook = renderHook(() =>
useTableColumns(all_columns, coltypes, data, undefined, [], {
col01: { id: 'ID' },
useTableColumns(colnames, coltypes, data, undefined, [], {
col01: { Header: 'Header' },
}),
);
expect(hook.result.current).toEqual([
{
Cell: expect.any(Function),
Header: 'col01',
Header: 'Header',
accessor: expect.any(Function),
id: 'ID',
id: 'col01',
},
{
Cell: expect.any(Function),
@ -173,27 +154,45 @@ test('useTableColumns with options', () => {
},
{
Cell: expect.any(Function),
Header: asciiKey,
Header: ASCII_KEY,
accessor: expect.any(Function),
id: asciiKey,
id: ASCII_KEY,
},
{
Cell: expect.any(Function),
Header: unicodeKey,
Header: UNICODE_KEY,
accessor: expect.any(Function),
id: unicodeKey,
id: UNICODE_KEY,
},
{
Cell: expect.any(Function),
Header: expect.objectContaining({
type: expect.objectContaining({
name: 'DataTableTemporalHeaderCell',
}),
props: expect.objectContaining({
originalFormattedTimeColumnIndex: -1,
}),
}),
accessor: expect.any(Function),
id: NUMTIME_KEY,
},
{
Cell: expect.any(Function),
Header: STRTIME_KEY,
accessor: expect.any(Function),
id: STRTIME_KEY,
},
]);
hook.result.current.forEach((col: JsonObject) => {
expect(col.accessor(data[0])).toBe(data[0][col.Header]);
expect(col.accessor(data[0])).toBe(data[0][col.id]);
});
hook.result.current.forEach((col: JsonObject) => {
data.forEach(row => {
expect(col.Cell({ value: row.col01 })).toBe(BOOL_TRUE_DISPLAY);
expect(col.Cell({ value: row.col02 })).toBe(BOOL_FALSE_DISPLAY);
expect(col.Cell({ value: row[asciiKey] })).toBe(asciiKey);
expect(col.Cell({ value: row[unicodeKey] })).toBe(unicodeKey);
expect(col.Cell({ value: row[col.id] })).toBe(
expectedDisplayValues[col.id],
);
});
});
});

View File

@ -151,7 +151,7 @@ describe('DataTablesPane', () => {
useRedux: true,
initialState: {
explore: {
timeFormattedColumns: {
originalFormattedTimeColumns: {
'34__table': ['__timestamp'],
},
},
@ -203,7 +203,7 @@ describe('DataTablesPane', () => {
useRedux: true,
initialState: {
explore: {
timeFormattedColumns: {
originalFormattedTimeColumns: {
'34__table': ['__timestamp'],
},
},

View File

@ -52,7 +52,7 @@ import {
useTableColumns,
} from 'src/explore/components/DataTableControl';
import { applyFormattingToTabularData } from 'src/utils/common';
import { useTimeFormattedColumns } from '../useTimeFormattedColumns';
import { useOriginalFormattedTimeColumns } from '../useOriginalFormattedTimeColumns';
const RESULT_TYPES = {
results: 'results' as const,
@ -147,7 +147,8 @@ const DataTable = ({
errorMessage,
type,
}: DataTableProps) => {
const timeFormattedColumns = useTimeFormattedColumns(datasource);
const originalFormattedTimeColumns =
useOriginalFormattedTimeColumns(datasource);
// this is to preserve the order of the columns, even if there are integer values,
// while also only grabbing the first column's keys
const columns = useTableColumns(
@ -155,7 +156,7 @@ const DataTable = ({
columnTypes,
data,
datasource,
timeFormattedColumns,
originalFormattedTimeColumns,
);
const filteredData = useFilteredTableData(filterText, data);
@ -210,10 +211,11 @@ const TableControls = ({
columnNames: string[];
isLoading: boolean;
}) => {
const timeFormattedColumns = useTimeFormattedColumns(datasourceId);
const originalFormattedTimeColumns =
useOriginalFormattedTimeColumns(datasourceId);
const formattedData = useMemo(
() => applyFormattingToTabularData(data, timeFormattedColumns),
[data, timeFormattedColumns],
() => applyFormattingToTabularData(data, originalFormattedTimeColumns),
[data, originalFormattedTimeColumns],
);
return (
<TableControlsWrapper>

View File

@ -19,9 +19,9 @@
import { useSelector } from 'react-redux';
import { ExplorePageState } from '../reducers/getInitialState';
export const useTimeFormattedColumns = (datasourceId?: string) =>
export const useOriginalFormattedTimeColumns = (datasourceId?: string) =>
useSelector<ExplorePageState, string[]>(state =>
datasourceId
? state.explore.timeFormattedColumns?.[datasourceId] ?? []
? state.explore.originalFormattedTimeColumns?.[datasourceId] ?? []
: [],
);

View File

@ -265,41 +265,51 @@ export default function exploreReducer(state = {}, action) {
sliceName: action.slice.slice_name ?? state.sliceName,
};
},
[actions.SET_TIME_FORMATTED_COLUMN]() {
[actions.SET_ORIGINAL_FORMATTED_TIME_COLUMN]() {
const { datasourceId, columnName } = action;
const newTimeFormattedColumns = { ...state.timeFormattedColumns };
const newTimeFormattedColumnsForDatasource = ensureIsArray(
newTimeFormattedColumns[datasourceId],
const newOriginalFormattedColumns = {
...state.originalFormattedTimeColumns,
};
const newOriginalFormattedColumnsForDatasource = ensureIsArray(
newOriginalFormattedColumns[datasourceId],
).slice();
newTimeFormattedColumnsForDatasource.push(columnName);
newTimeFormattedColumns[datasourceId] =
newTimeFormattedColumnsForDatasource;
newOriginalFormattedColumnsForDatasource.push(columnName);
newOriginalFormattedColumns[datasourceId] =
newOriginalFormattedColumnsForDatasource;
setItem(
LocalStorageKeys.explore__data_table_time_formatted_columns,
newTimeFormattedColumns,
LocalStorageKeys.explore__data_table_original_formatted_time_columns,
newOriginalFormattedColumns,
);
return { ...state, timeFormattedColumns: newTimeFormattedColumns };
return {
...state,
originalFormattedTimeColumns: newOriginalFormattedColumns,
};
},
[actions.UNSET_TIME_FORMATTED_COLUMN]() {
[actions.UNSET_ORIGINAL_FORMATTED_TIME_COLUMN]() {
const { datasourceId, columnIndex } = action;
const newTimeFormattedColumns = { ...state.timeFormattedColumns };
const newTimeFormattedColumnsForDatasource = ensureIsArray(
newTimeFormattedColumns[datasourceId],
const newOriginalFormattedColumns = {
...state.originalFormattedTimeColumns,
};
const newOriginalFormattedColumnsForDatasource = ensureIsArray(
newOriginalFormattedColumns[datasourceId],
).slice();
newTimeFormattedColumnsForDatasource.splice(columnIndex, 1);
newTimeFormattedColumns[datasourceId] =
newTimeFormattedColumnsForDatasource;
newOriginalFormattedColumnsForDatasource.splice(columnIndex, 1);
newOriginalFormattedColumns[datasourceId] =
newOriginalFormattedColumnsForDatasource;
if (newTimeFormattedColumnsForDatasource.length === 0) {
delete newTimeFormattedColumns[datasourceId];
if (newOriginalFormattedColumnsForDatasource.length === 0) {
delete newOriginalFormattedColumns[datasourceId];
}
setItem(
LocalStorageKeys.explore__data_table_time_formatted_columns,
newTimeFormattedColumns,
LocalStorageKeys.explore__data_table_original_formatted_time_columns,
newOriginalFormattedColumns,
);
return { ...state, timeFormattedColumns: newTimeFormattedColumns };
return {
...state,
originalFormattedTimeColumns: newOriginalFormattedColumns,
};
},
[actions.SET_FORCE_QUERY]() {
return {

View File

@ -78,8 +78,8 @@ export default function getInitialState(
initialFormData,
) as ControlStateMapping,
controlsTransferred: [],
timeFormattedColumns: getItem(
LocalStorageKeys.explore__data_table_time_formatted_columns,
originalFormattedTimeColumns: getItem(
LocalStorageKeys.explore__data_table_original_formatted_time_columns,
{},
),
};

View File

@ -49,7 +49,7 @@ export enum LocalStorageKeys {
* sqllab__is_autocomplete_enabled
*/
sqllab__is_autocomplete_enabled = 'sqllab__is_autocomplete_enabled',
explore__data_table_time_formatted_columns = 'explore__data_table_time_formatted_columns',
explore__data_table_original_formatted_time_columns = 'explore__data_table_original_formatted_time_columns',
}
export type LocalStorageValues = {
@ -63,7 +63,7 @@ export type LocalStorageValues = {
homepage_collapse_state: string[];
homepage_activity_filter: SetTabType | null;
sqllab__is_autocomplete_enabled: boolean;
explore__data_table_time_formatted_columns: Record<string, string[]>;
explore__data_table_original_formatted_time_columns: Record<string, string[]>;
};
export function getItem<K extends LocalStorageKeys>(