diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/Table/utils.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/Table/utils.ts index d6db5b058e..80630541c5 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/Table/utils.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/Table/utils.ts @@ -174,7 +174,15 @@ export function getTableTitle(config: TableWidgetData['config']): TableTitle | u } export function getTableSizes(table: HTMLTableElement) { - const tableScale = round(table?.getBoundingClientRect()?.width / table?.clientWidth, 2); + // Calculate scale factor to handle CSS transforms. + // For very wide tables, Firefox may return incorrect getBoundingClientRect values, + // so we validate that tableScale is within reasonable bounds (0.5 to 2.0) + let tableScale = round(table?.getBoundingClientRect()?.width / table?.clientWidth, 2); + + if (!Number.isFinite(tableScale) || tableScale < 0.5 || tableScale > 2) { + tableScale = 1; + } + let rows: HTMLTableRowElement[] = []; rows = Array.from( diff --git a/src/ui/units/connections/components/custom-forms/utils/render.tsx b/src/ui/units/connections/components/custom-forms/utils/render.tsx index 2be5500da0..2f13e002cc 100644 --- a/src/ui/units/connections/components/custom-forms/utils/render.tsx +++ b/src/ui/units/connections/components/custom-forms/utils/render.tsx @@ -20,6 +20,7 @@ const isTitleMatchedByFilter = (title: string, filter: string) => { const BASE_CELL_CSS = { height: '38px', borderBottom: '1px solid var(--g-color-line-generic)', + whiteSpace: 'nowrap', }; const FIRST_CELL_CSS = { diff --git a/src/ui/units/datasets/components/PreviewHeader/PreviewHeader.tsx b/src/ui/units/datasets/components/PreviewHeader/PreviewHeader.tsx index 379903f1eb..bb988f1ed8 100644 --- a/src/ui/units/datasets/components/PreviewHeader/PreviewHeader.tsx +++ b/src/ui/units/datasets/components/PreviewHeader/PreviewHeader.tsx @@ -1,11 +1,12 @@ import React from 'react'; import {ChevronsExpandUpRight, LayoutHeader, LayoutSideContent, Xmark} from '@gravity-ui/icons'; -import {Button, Icon, TextInput} from '@gravity-ui/uikit'; +import {Button, Icon, TextInput, type TextInputProps} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import {i18n} from 'i18n'; import _debounce from 'lodash/debounce'; import {formatNumber} from 'shared/modules/format-units/formatUnit'; +import type {DatasetPreviewView} from 'units/datasets/store/types'; import {VIEW_PREVIEW} from '../../constants'; @@ -20,19 +21,37 @@ const ICON_WIDTH = 24; interface Props { amountPreviewRows: number; view: string; - toggleViewPreview: (args: {view: string}) => void; + toggleViewPreview: (args: {view: DatasetPreviewView}) => void; closePreview: () => void; - changeAmountPreviewRows: (args: {amountPreviewRows: string}) => void; + changeAmountPreviewRows: (args: {amountPreviewRows: number}) => void; refetchPreviewDataset: () => void; } -class PreviewHeader extends React.Component { +interface State { + internalAmountPreviewRows: string; +} + +const TEXT_INPUT_CONTROL_PROPS: TextInputProps['controlProps'] = { + min: 0, + step: 1, +}; + +class PreviewHeader extends React.Component { static defaultProps = {}; private debouncedChangeAmountPreviewRows = _debounce(this.props.refetchPreviewDataset, 1000); + constructor(props: Props) { + super(props); + + this.state = { + internalAmountPreviewRows: props.amountPreviewRows.toString(), + }; + } + render() { - const {view, amountPreviewRows} = this.props; + const {view} = this.props; + const {internalAmountPreviewRows} = this.state; return (
@@ -44,8 +63,9 @@ class PreviewHeader extends React.Component { {i18n('dataset.dataset-editor.modify', 'label_max-amount-rows', { @@ -105,18 +125,26 @@ class PreviewHeader extends React.Component { ); } - changeAmountPreviewRows = (amountPreviewRows: string) => { + changeAmountPreviewRows = (amountPreviewRowsString: string) => { const {changeAmountPreviewRows} = this.props; if (this.debouncedChangeAmountPreviewRows) { this.debouncedChangeAmountPreviewRows.cancel(); } - if (shouldFetchPreview(amountPreviewRows)) { + if (shouldFetchPreview(amountPreviewRowsString)) { this.debouncedChangeAmountPreviewRows(); } - changeAmountPreviewRows({amountPreviewRows}); + this.setState({internalAmountPreviewRows: amountPreviewRowsString}); + + const amountPreviewRowsNumber = parseInt(amountPreviewRowsString, 10); + + if (Number.isNaN(amountPreviewRowsNumber)) { + return; + } + + changeAmountPreviewRows({amountPreviewRows: amountPreviewRowsNumber}); }; togglePreviewFull = () => { diff --git a/src/ui/units/datasets/components/PreviewTable/PreviewTable.js b/src/ui/units/datasets/components/PreviewTable/PreviewTable.js deleted file mode 100644 index 0246c97c52..0000000000 --- a/src/ui/units/datasets/components/PreviewTable/PreviewTable.js +++ /dev/null @@ -1,252 +0,0 @@ -import React from 'react'; - -import {Hashtag} from '@gravity-ui/icons'; -import DataTable from '@gravity-ui/react-data-table'; -import {Button, Icon} from '@gravity-ui/uikit'; -import block from 'bem-cn-lite'; -import {i18n} from 'i18n'; -import PropTypes from 'prop-types'; -import {connect} from 'react-redux'; -import {openDialogErrorWithTabs} from 'store/actions/dialog'; -import {Utils} from 'ui'; - -import Markup from '../../../../components/Markup/Markup'; -import {BI_ERRORS} from '../../../../constants'; -import ContainerLoader from '../../components/ContainerLoader/ContainerLoader'; - -import './PreviewTable.scss'; - -const b = block('preview-table'); - -class PreviewTable extends React.Component { - _collator = new Intl.Collator(undefined, { - numeric: true, - }); - - get textPreviewLoader() { - const {preview: {readyPreview} = {}} = this.props; - - switch (readyPreview.toLowerCase()) { - case 'pending': - return i18n('dataset.dataset-editor.modify', 'label_data-preparation-preview'); - case 'loading': - default: - return i18n('dataset.dataset-editor.modify', 'label_loading-dataset-preview'); - } - } - - getRows() { - const {preview: {data: {Data = []} = {}} = {}} = this.props; - - return Data.map((row, index) => { - const preparedRow = row.map((item, itemIndex) => { - if (item && typeof item === 'object') { - // In this place, except for the markup in the form of an object, nothing comes - return ; - } - - return item; - }); - - return Object.assign( - { - positionIndex: index + 1, - }, - preparedRow, - ); - }); - } - - getColumns() { - const {preview: {data: {Type = []} = {}} = {}} = this.props; - - const columns = Type[1][1].reduce((columnsTable, column, index) => { - const [name, type] = column; - - columnsTable.push({ - name: index, - header: name, - type, - }); - - return columnsTable; - }, []); - - columns.unshift({ - name: 'positionIndex', - header: , - }); - - return columns; - } - - getTableData() { - try { - const rows = this.getRows(); - const columns = this.getColumns(); - - return { - columns: this.handleColumns(columns), - rows, - startIndex: 0, - }; - } catch (error) { - return { - columns: [], - rows: [], - startIndex: 0, - }; - } - } - - sortRows = - (columnName) => - ({row: rowCurrent}, {row: rowNext}) => { - const valueCurrent = rowCurrent[columnName]; - const valueNext = rowNext[columnName]; - - return this._collator.compare(valueCurrent, valueNext); - }; - - handleColumns(columns) { - return columns.map((column) => { - const {header, name: columnName} = column; - - const sortAscending = this.sortRows(columnName); - - return { - ...column, - header:
{header}
, - className: b('column'), - render: ({value}) => value, - sortAscending, - customStyle: ({header}) => { - const generalStyle = { - paddingTop: '0', - paddingBottom: '0', - }; - - if (header) { - return { - ...generalStyle, - background: 'var(--ds-color-base-area)', - borderTop: '1px solid var(--ds-color-divider)', - borderBottom: '1px solid var(--ds-color-divider)', - }; - } - }, - }; - }); - } - - getErrorMessage(code) { - switch (code) { - case BI_ERRORS.MATERIALIZATION_NOT_FINISHED: - case BI_ERRORS.DATA_PREPARATION_NOT_FINISHED: - return i18n( - 'component.chartkit-error.codes', - 'ERR.DS_API.DB.DATA_PREPARATION_NOT_FINISHED', - ); - case BI_ERRORS.NO_AVAILABLE_SUBPRODUCTS: - return i18n( - 'component.chartkit-error.codes', - 'ERR.DS_API.NO_AVAILABLE_SUBPRODUCTS', - ); - default: - return i18n('dataset.dataset-editor.modify', 'label_request-dataset-preview-error'); - } - } - - renderError() { - const {error} = this.props; - const {code} = Utils.parseErrorResponse(error); - - return ( -
-
- {this.getErrorMessage(code)} - {/* - For incomplete data preparation, we do not show the button with details - because it's kind of not a mistake - */} - {code !== BI_ERRORS.DATA_PREPARATION_NOT_FINISHED && - code !== BI_ERRORS.MATERIALIZATION_NOT_FINISHED && ( - - )} -
-
- ); - } - - render() { - const {preview: {isLoading, readyPreview} = {}, view} = this.props; - - const isDisplayError = ['failed'].includes(readyPreview); - - if (isLoading) { - return ( -
-
- -
-
- ); - } - - if (isDisplayError) { - return this.renderError(); - } - - const {columns, rows} = this.getTableData(); - - const isNoData = !rows.length && !columns.length; - - return ( -
- b('row')} - theme={'preview-dataset'} - /> -
- ); - } -} - -PreviewTable.propTypes = { - view: PropTypes.string.isRequired, - preview: PropTypes.object.isRequired, - error: PropTypes.object, - openDialogErrorWithTabs: PropTypes.func.isRequired, -}; - -const mapDispatchToProps = { - openDialogErrorWithTabs, -}; - -export default connect(null, mapDispatchToProps)(PreviewTable); diff --git a/src/ui/units/datasets/components/PreviewTable/PreviewTable.scss b/src/ui/units/datasets/components/PreviewTable/PreviewTable.scss index 2d5809e8b5..b3686208d8 100644 --- a/src/ui/units/datasets/components/PreviewTable/PreviewTable.scss +++ b/src/ui/units/datasets/components/PreviewTable/PreviewTable.scss @@ -36,21 +36,6 @@ font-size: 14px; } - &__header { - height: 32px; - display: flex; - align-items: center; - } - - &__column { - vertical-align: middle; - } - - &__row { - height: 32px; - border-bottom: 1px solid var(--ds-color-divider); - } - &__details-btn { margin-left: 12px; } diff --git a/src/ui/units/datasets/components/PreviewTable/PreviewTable.tsx b/src/ui/units/datasets/components/PreviewTable/PreviewTable.tsx new file mode 100644 index 0000000000..5a4e50723c --- /dev/null +++ b/src/ui/units/datasets/components/PreviewTable/PreviewTable.tsx @@ -0,0 +1,230 @@ +import React from 'react'; + +import {Hashtag} from '@gravity-ui/icons'; +import {Button, Icon} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {i18n} from 'i18n'; +import {useDispatch} from 'react-redux'; +import type {TableCellsRow, TableColumn} from 'shared/types/chartkit/table'; +import {openDialogErrorWithTabs} from 'store/actions/dialog'; +import {Utils} from 'ui'; +import TableWidget from 'ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/TableWidget'; +import type {TableWidgetData} from 'ui/libs/DatalensChartkit/types'; +import type {DatasetError, DatasetPreview, DatasetPreviewView} from 'units/datasets/store/types'; + +import {BI_ERRORS} from '../../../../constants'; +import ContainerLoader from '../ContainerLoader/ContainerLoader'; + +import './PreviewTable.scss'; + +const b = block('preview-table'); + +const BASE_CELL_CSS = { + height: '32px', + borderBottom: '1px solid var(--ds-color-divider)', + whiteSpace: 'nowrap', + paddingTop: '0', + paddingBottom: '0', +}; + +const HEADER_CELL_CSS = { + ...BASE_CELL_CSS, + background: 'var(--ds-color-base-area)', + borderTop: '1px solid var(--ds-color-divider)', +}; + +const BASE_COLUMN_PARAMETERS: Pick = { + sortable: false, + verticalAlignment: 'center', + css: HEADER_CELL_CSS, +}; + +const INDEX_COLUMN: TableColumn = { + id: 'positionIndex', + type: 'text', + name: 'positionIndex', + formattedName: , + ...BASE_COLUMN_PARAMETERS, +}; + +type Props = { + view: DatasetPreviewView; + preview: DatasetPreview; + error?: DatasetError; +}; + +function PreviewTable(props: Props) { + const dispatch = useDispatch(); + + const {error, preview, view} = props; + const {isLoading, readyPreview} = preview; + + const getTextPreviewLoader = () => { + if (!readyPreview) { + return null; + } + + switch (readyPreview.toLowerCase()) { + case 'pending': + return i18n('dataset.dataset-editor.modify', 'label_data-preparation-preview'); + case 'loading': + default: + return i18n('dataset.dataset-editor.modify', 'label_loading-dataset-preview'); + } + }; + + const columns = React.useMemo((): TableColumn[] => { + const {data: {Type = []} = {}} = preview; + const typeColumns = Type[1]?.[1]; + + if (!Array.isArray(typeColumns) || typeColumns.length < 1) { + return []; + } + + return typeColumns.reduce((acc: TableColumn[], column, index) => { + const [name] = column; + + if (index === 0) { + acc.push(INDEX_COLUMN); + } + + acc.push({ + id: name, + type: 'text', + name, + custom: {originalIndex: index}, + ...BASE_COLUMN_PARAMETERS, + }); + + return acc; + }, []); + }, [preview]); + + const rows = React.useMemo((): TableCellsRow[] => { + const {data: {Data = []} = {}} = preview; + + return Data.map((rowCells, rowIndex) => { + return { + cells: columns.map((column) => { + const {id, custom} = column; + const originalIndex = custom?.originalIndex; + + return { + fieldId: id, + value: + typeof originalIndex === 'number' + ? rowCells[originalIndex] + : rowIndex + 1, + verticalAlignment: 'center', + css: BASE_CELL_CSS, + }; + }), + }; + }); + }, [columns, preview]); + + const tableWidgetData = React.useMemo((): TableWidgetData => { + return { + data: { + head: columns, + rows, + }, + params: {}, + type: 'table', + controls: { + controls: [], + }, + }; + }, [columns, rows]); + + const getErrorMessage = (code: string) => { + switch (code) { + case BI_ERRORS.MATERIALIZATION_NOT_FINISHED: + case BI_ERRORS.DATA_PREPARATION_NOT_FINISHED: + return i18n( + 'component.chartkit-error.codes', + 'ERR.DS_API.DB.DATA_PREPARATION_NOT_FINISHED', + ); + case BI_ERRORS.NO_AVAILABLE_SUBPRODUCTS: + return i18n( + 'component.chartkit-error.codes', + 'ERR.DS_API.NO_AVAILABLE_SUBPRODUCTS', + ); + default: + return i18n('dataset.dataset-editor.modify', 'label_request-dataset-preview-error'); + } + }; + + const renderError = () => { + const {code} = Utils.parseErrorResponse(error); + + return ( +
+
+ {getErrorMessage(code)} + {/* + For incomplete data preparation, we do not show the button with details + because it's kind of not a mistake + */} + {code !== BI_ERRORS.DATA_PREPARATION_NOT_FINISHED && + code !== BI_ERRORS.MATERIALIZATION_NOT_FINISHED && ( + + )} +
+
+ ); + }; + + const isDisplayError = readyPreview === 'failed'; + + if (isLoading) { + const textPreviewLoader = getTextPreviewLoader(); + + return ( +
+
+ +
+
+ ); + } + + if (isDisplayError) { + return renderError(); + } + + const isNoData = !tableWidgetData.data.head?.length && !tableWidgetData.data.rows?.length; + + return ( +
+ +
+ ); +} + +PreviewTable.displayName = 'PreviewTable'; + +export default React.memo(PreviewTable); diff --git a/src/ui/units/datasets/constants/index.ts b/src/ui/units/datasets/constants/index.ts index 9bf706567d..4edd70200e 100644 --- a/src/ui/units/datasets/constants/index.ts +++ b/src/ui/units/datasets/constants/index.ts @@ -2,6 +2,7 @@ import {i18n} from 'i18n'; import type {CollectionId, EntryScope, WorkbookId} from 'shared'; import type {GetEntryResponse} from 'shared/schema'; import {DL} from 'ui'; +import type {DatasetPreviewView} from 'units/datasets/store/types'; import {getFakeEntry as genericGetFakeEntry} from '../../../components/ActionPanel'; @@ -236,7 +237,7 @@ export const THEME = { DARK: 'dark', }; -export const VIEW_PREVIEW = { +export const VIEW_PREVIEW: Record = { FULL: 'full', BOTTOM: 'bottom', RIGHT: 'right', diff --git a/src/ui/units/datasets/containers/DatasetPreview/DatasetPreview.js b/src/ui/units/datasets/containers/DatasetPreview/DatasetPreview.js deleted file mode 100644 index f65e656d8a..0000000000 --- a/src/ui/units/datasets/containers/DatasetPreview/DatasetPreview.js +++ /dev/null @@ -1,92 +0,0 @@ -import React from 'react'; - -import block from 'bem-cn-lite'; -import throttle from 'lodash/throttle'; -import PropTypes from 'prop-types'; -import {connect} from 'react-redux'; -import {compose} from 'recompose'; -import {createStructuredSelector} from 'reselect'; -import { - changeAmountPreviewRows, - refetchPreviewDataset, - toggleViewPreview, -} from 'units/datasets/store/actions/creators'; - -import {ChartKitTooltip} from '../../../../libs/DatalensChartkit/ChartKit/components'; -import PreviewHeader from '../../components/PreviewHeader/PreviewHeader'; -import PreviewTable from '../../components/PreviewTable/PreviewTable'; -import {datasetPreviewErrorSelector, datasetPreviewSelector} from '../../store/selectors/dataset'; - -import './DatasetPreview.scss'; - -const b = block('dataset-preview'); - -const DatasetPreview = (props) => { - const { - datasetPreview, - previewError, - datasetPreview: {view, amountPreviewRows} = {}, - toggleViewPreview, - changeAmountPreviewRows, - refetchPreviewDataset, - closePreview, - } = props; - - const rootNodeRef = React.useRef(null); - const tooltipRef = React.useRef(null); - - const handleContainerMousemove = React.useCallback((e) => { - tooltipRef.current?.checkForTooltipNode(e); - }, []); - - React.useEffect(() => { - const throttledHandler = throttle(handleContainerMousemove, 200); - const container = rootNodeRef.current; - - if (container) { - rootNodeRef.current.addEventListener('mousemove', throttledHandler); - } - - return () => { - if (container) { - container.removeEventListener('mousemove', throttledHandler); - } - }; - }, [rootNodeRef, handleContainerMousemove]); - - return ( -
- - - -
- ); -}; - -DatasetPreview.propTypes = { - datasetPreview: PropTypes.object.isRequired, - previewError: PropTypes.object, - toggleViewPreview: PropTypes.func.isRequired, - closePreview: PropTypes.func.isRequired, - changeAmountPreviewRows: PropTypes.func.isRequired, - refetchPreviewDataset: PropTypes.func.isRequired, -}; - -const mapStateToProps = createStructuredSelector({ - datasetPreview: datasetPreviewSelector, - previewError: datasetPreviewErrorSelector, -}); -const mapDispatchToProps = { - toggleViewPreview, - changeAmountPreviewRows, - refetchPreviewDataset, -}; - -export default compose(connect(mapStateToProps, mapDispatchToProps))(DatasetPreview); diff --git a/src/ui/units/datasets/containers/DatasetPreview/DatasetPreview.tsx b/src/ui/units/datasets/containers/DatasetPreview/DatasetPreview.tsx new file mode 100644 index 0000000000..70a6bca396 --- /dev/null +++ b/src/ui/units/datasets/containers/DatasetPreview/DatasetPreview.tsx @@ -0,0 +1,97 @@ +import React from 'react'; + +import block from 'bem-cn-lite'; +import throttle from 'lodash/throttle'; +import {useDispatch, useSelector} from 'react-redux'; +import { + changeAmountPreviewRows, + refetchPreviewDataset, + toggleViewPreview, +} from 'units/datasets/store/actions/creators'; + +import { + ChartKitTooltip, + type ChartKitTooltipRef, +} from '../../../../libs/DatalensChartkit/ChartKit/components'; +import PreviewHeader from '../../components/PreviewHeader/PreviewHeader'; +import PreviewTable from '../../components/PreviewTable/PreviewTable'; +import {datasetPreviewErrorSelector, datasetPreviewSelector} from '../../store/selectors/dataset'; + +import './DatasetPreview.scss'; + +const b = block('dataset-preview'); + +type Props = { + previewError?: Object; + closePreview: () => void; +}; + +const DatasetPreview = (props: Props) => { + const dispatch = useDispatch(); + + const {closePreview} = props; + + const datasetPreview = useSelector(datasetPreviewSelector); + const previewError = useSelector(datasetPreviewErrorSelector); + + const {view, amountPreviewRows} = datasetPreview; + + const rootNodeRef = React.useRef(null); + const tooltipRef = React.useRef(null); + + const handleContainerMousemove = React.useCallback((e) => { + tooltipRef.current?.checkForTooltipNode(e); + }, []); + + const handleToggleViewPreview = React.useCallback( + (args) => { + return dispatch(toggleViewPreview(args)); + }, + [dispatch], + ); + + const handleChangeAmountPreviewRows = React.useCallback( + (args) => { + return dispatch(changeAmountPreviewRows(args)); + }, + [dispatch], + ); + + const handleRefetchPreviewDataset = React.useCallback(() => { + return dispatch(refetchPreviewDataset()); + }, [dispatch]); + + React.useEffect(() => { + const throttledHandler = throttle(handleContainerMousemove, 200); + const container = rootNodeRef.current; + + if (container) { + rootNodeRef.current?.addEventListener('mousemove', throttledHandler); + } + + return () => { + if (container) { + container.removeEventListener('mousemove', throttledHandler); + } + }; + }, [rootNodeRef, handleContainerMousemove]); + + return ( +
+ + + +
+ ); +}; + +DatasetPreview.displayName = 'DatasetPreview'; + +export default React.memo(DatasetPreview); diff --git a/src/ui/units/datasets/store/constants.ts b/src/ui/units/datasets/store/constants.ts index a8d37d733a..3c614d44be 100644 --- a/src/ui/units/datasets/store/constants.ts +++ b/src/ui/units/datasets/store/constants.ts @@ -4,7 +4,7 @@ import type {DatasetTab} from '../constants'; import {DATASET_TABS, TAB_DATASET, TAB_SOURCES} from '../constants'; import DatasetUtils, {isCreationProcess} from '../helpers/utils'; -import type {DatasetReduxState} from './types'; +import type {DatasetPreview, DatasetReduxState} from './types'; const getDefaultDatasetContent = (): Partial => ({ avatar_relations: [], @@ -34,14 +34,17 @@ export const getCurrentTab = (): DatasetTab => { return defaultTab; }; -export const initialPreview: DatasetReduxState['preview'] = { +export const initialPreview: DatasetPreview = { previewEnabled: true, readyPreview: 'loading', isVisible: true, isLoading: true, amountPreviewRows: 10, view: 'bottom', - data: [], + data: { + Data: [], + Type: ['ListType', ['StructType', []]], + }, error: null, isQueued: false, }; diff --git a/src/ui/units/datasets/store/types/dataset.ts b/src/ui/units/datasets/store/types/dataset.ts index 5e1072adc1..193e0f1a44 100644 --- a/src/ui/units/datasets/store/types/dataset.ts +++ b/src/ui/units/datasets/store/types/dataset.ts @@ -272,6 +272,23 @@ export type SourcesPagination = { searchValue: string; }; +export type DatasetPreviewView = 'full' | 'bottom' | 'right'; + +export type DatasetPreview = { + previewEnabled: boolean; + readyPreview: 'loading' | 'failed' | null; + isVisible: boolean; + isLoading: boolean; + amountPreviewRows: number; + view: DatasetPreviewView; + data: { + Data: string[][]; + Type: ['ListType', ['StructType', Array<[string, ['OptionalType', ['DataType', string]]]>]]; + }; + error: DatasetError; + isQueued: boolean; +}; + export type DatasetReduxState = { isRefetchingDataset: boolean; isLoading: boolean; @@ -293,17 +310,7 @@ export type DatasetReduxState = { connectionsDbNames: Record; sourcesPagination: SourcesPagination; sourceListingOptions?: SourceListingOptions['source_listing']; - preview: { - previewEnabled: boolean; - readyPreview: 'loading' | 'failed' | null; - isVisible: boolean; - isLoading: boolean; - amountPreviewRows: number; - view: 'full' | 'bottom' | 'right'; - data: string[]; // TODO: correctly describe the type - error: DatasetError; - isQueued: boolean; - }; + preview: DatasetPreview; errors: { previewError: DatasetError; savingError: DatasetError;