From 1fb98cb246efb1ad0f7d54acc025342a2862a1e7 Mon Sep 17 00:00:00 2001 From: Vlad Babich Date: Mon, 29 Apr 2024 19:15:57 -0600 Subject: [PATCH 1/9] Fix null partition filter --- packages/iris-grid/src/IrisGrid.tsx | 44 ++++++++--------------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/packages/iris-grid/src/IrisGrid.tsx b/packages/iris-grid/src/IrisGrid.tsx index 6299f53c0..6b8478bed 100644 --- a/packages/iris-grid/src/IrisGrid.tsx +++ b/packages/iris-grid/src/IrisGrid.tsx @@ -2043,43 +2043,23 @@ class IrisGrid extends Component { model.partitionKeysTable(), resolved => resolved.close() ); - const { dh } = model; const sorts = keyTable.columns.map(column => column.sort().desc()); keyTable.applySort(sorts); keyTable.setViewport(0, 0); - return new Promise((resolve, reject) => { - // We want to wait for the first UPDATED event instead of just getting viewport data here - // It's possible that the key table does not have any rows of data yet, so just wait until it does have one - keyTable.addEventListener( - dh.Table.EVENT_UPDATED, - (event: CustomEvent) => { - try { - const { detail: data } = event; - if (data.rows.length === 0) { - // Table is empty, wait for the next updated event - return; - } - const row = data.rows[0]; - // Core JSAPI returns undefined for null table values, IrisGridPartitionSelector expects null - // https://github.com/deephaven/deephaven-core/issues/5400 - const values = keyTable.columns.map( - column => row.get(column) ?? null - ); - const newPartition: PartitionConfig = { - partitions: values, - mode: 'partition', - }; - keyTable.close(); - resolve(newPartition); - } catch (e) { - keyTable.close(); - reject(e); - } - } - ); - }); + const { rows } = await keyTable.getViewportData(); + let values = []; + if (rows.length > 0) { + const row = rows[0]; + values = keyTable.columns.map(column => row.get(column)); + } + const newPartition: PartitionConfig = { + partitions: values, + mode: 'partition', + }; + keyTable.close(); + return newPartition; } copyCell( From 6a7cb6c298d3a2e8a3d16c6d18fb8dcef4a2280f Mon Sep 17 00:00:00 2001 From: Vlad Babich Date: Mon, 29 Apr 2024 23:27:36 -0600 Subject: [PATCH 2/9] Add IrisGridEmptyTableModel representing an empty table with a given table schema --- packages/iris-grid/src/IrisGrid.tsx | 5 +++- .../iris-grid/src/IrisGridEmptyTableModel.ts | 26 +++++++++++++++++++ packages/iris-grid/src/IrisGridProxyModel.ts | 15 +++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 packages/iris-grid/src/IrisGridEmptyTableModel.ts diff --git a/packages/iris-grid/src/IrisGrid.tsx b/packages/iris-grid/src/IrisGrid.tsx index 6b8478bed..de7a7f472 100644 --- a/packages/iris-grid/src/IrisGrid.tsx +++ b/packages/iris-grid/src/IrisGrid.tsx @@ -2052,7 +2052,10 @@ class IrisGrid extends Component { let values = []; if (rows.length > 0) { const row = rows[0]; - values = keyTable.columns.map(column => row.get(column)); + // Core JSAPI returns undefined for null table values, + // coalesce with null to differentiate null from no selection in the dropdown + // https://github.com/deephaven/deephaven-core/issues/5400 + values = keyTable.columns.map(column => row.get(column) ?? null); } const newPartition: PartitionConfig = { partitions: values, diff --git a/packages/iris-grid/src/IrisGridEmptyTableModel.ts b/packages/iris-grid/src/IrisGridEmptyTableModel.ts new file mode 100644 index 000000000..14f1aa9e0 --- /dev/null +++ b/packages/iris-grid/src/IrisGridEmptyTableModel.ts @@ -0,0 +1,26 @@ +/* eslint class-methods-use-this: "off" */ +import type { dh as DhType } from '@deephaven/jsapi-types'; +import { Formatter } from '@deephaven/jsapi-utils'; +import IrisGridTableModel from './IrisGridTableModel'; + +/** Model that represents an empty table with a schema from the provided table */ +class IrisGridEmptyTableModel extends IrisGridTableModel { + /** + * @param dh JSAPI instance + * @param table Table to be used in the model + * @param formatter The formatter to use when getting formats + */ + constructor( + dh: typeof DhType, + table: DhType.Table, + formatter = new Formatter(dh) + ) { + super(dh, table, formatter); + } + + get size(): number { + return 0; + } +} + +export default IrisGridEmptyTableModel; diff --git a/packages/iris-grid/src/IrisGridProxyModel.ts b/packages/iris-grid/src/IrisGridProxyModel.ts index a934d38c1..df96bcaeb 100644 --- a/packages/iris-grid/src/IrisGridProxyModel.ts +++ b/packages/iris-grid/src/IrisGridProxyModel.ts @@ -33,6 +33,7 @@ import { PartitionedGridModel, isPartitionedGridModelProvider, } from './PartitionedGridModel'; +import IrisGridEmptyTableModel from './IrisGridEmptyTableModel'; const log = Log.module('IrisGridProxyModel'); @@ -531,6 +532,20 @@ class IrisGridProxyModel extends IrisGridModel implements PartitionedGridModel { modelPromise = this.originalModel .partitionMergedTable() .then(table => makeModel(this.dh, table, this.formatter)); + } else if ( + partitionConfig.mode === 'partition' && + partitionConfig.partitions.length === 0 + ) { + // originalModel.partitionTable([]) would throw an error + // Return an empty model instead + modelPromise = this.originalModel + // TODO: + // Can we get a table with all columns without calling `partitionMergedTable`? + // Would have to implement all methods in `IrisGridEmptyTableModel` if we only pass columns instead of a table with a matching schema + .partitionMergedTable() + .then( + table => new IrisGridEmptyTableModel(this.dh, table, this.formatter) + ); } else { modelPromise = this.originalModel .partitionTable(partitionConfig.partitions) From c1d88c7b24f00153ce87bdb620c520b386dcebb2 Mon Sep 17 00:00:00 2001 From: Vlad Babich Date: Mon, 29 Apr 2024 23:34:56 -0600 Subject: [PATCH 3/9] Update comment --- packages/iris-grid/src/IrisGridProxyModel.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/iris-grid/src/IrisGridProxyModel.ts b/packages/iris-grid/src/IrisGridProxyModel.ts index df96bcaeb..daf73be30 100644 --- a/packages/iris-grid/src/IrisGridProxyModel.ts +++ b/packages/iris-grid/src/IrisGridProxyModel.ts @@ -539,9 +539,10 @@ class IrisGridProxyModel extends IrisGridModel implements PartitionedGridModel { // originalModel.partitionTable([]) would throw an error // Return an empty model instead modelPromise = this.originalModel + // .partitionMergedTable() is a workaround to get a table with the needed schema. + // It will not be expensive since there is no partitions. // TODO: - // Can we get a table with all columns without calling `partitionMergedTable`? - // Would have to implement all methods in `IrisGridEmptyTableModel` if we only pass columns instead of a table with a matching schema + // Can we get a table without `partitionMergedTable`? .partitionMergedTable() .then( table => new IrisGridEmptyTableModel(this.dh, table, this.formatter) From c0f50d2374706051ec61fbd4c9663395f4c5a884 Mon Sep 17 00:00:00 2001 From: Vlad Babich Date: Wed, 8 May 2024 17:11:09 -0600 Subject: [PATCH 4/9] Add IrisGridSchemaModelTemplate, update class hierarchy --- packages/iris-grid/src/EmptyIrisGridModel.ts | 23 +- .../src/IrisGridPartitionedTableModel.ts | 16 +- packages/iris-grid/src/IrisGridProxyModel.ts | 19 +- .../src/IrisGridSchemaModelTemplate.ts | 660 ++++++++++++++++++ 4 files changed, 675 insertions(+), 43 deletions(-) create mode 100644 packages/iris-grid/src/IrisGridSchemaModelTemplate.ts diff --git a/packages/iris-grid/src/EmptyIrisGridModel.ts b/packages/iris-grid/src/EmptyIrisGridModel.ts index 68c1471ae..00e46588b 100644 --- a/packages/iris-grid/src/EmptyIrisGridModel.ts +++ b/packages/iris-grid/src/EmptyIrisGridModel.ts @@ -7,7 +7,7 @@ import { } from '@deephaven/grid'; import { dh as DhType } from '@deephaven/jsapi-types'; import { ColumnName, Formatter } from '@deephaven/jsapi-utils'; -import { EMPTY_ARRAY, EMPTY_MAP } from '@deephaven/utils'; +import { EMPTY_ARRAY, EMPTY_MAP, EventShimCustomEvent } from '@deephaven/utils'; import IrisGridModel from './IrisGridModel'; import ColumnHeaderGroup from './ColumnHeaderGroup'; import { @@ -15,16 +15,14 @@ import { PendingDataMap, UITotalsTableConfig, } from './CommonTypes'; +import IrisGridSchemaModelTemplate from './IrisGridSchemaModelTemplate'; -class EmptyIrisGridModel extends IrisGridModel { +// TODO: delete all methods overlapping with IrisGridSchemaModelTemplate +class EmptyIrisGridModel extends IrisGridSchemaModelTemplate { constructor(dh: typeof DhType, formatter = new Formatter(dh)) { - super(dh); - - this.modelFormatter = formatter; + super(dh, []); } - modelFormatter: Formatter; - get rowCount(): number { return 0; } @@ -96,14 +94,6 @@ class EmptyIrisGridModel extends IrisGridModel { return EMPTY_ARRAY; } - get formatter(): Formatter { - return this.modelFormatter; - } - - set formatter(formatter: Formatter) { - this.modelFormatter = formatter; - } - displayString( value: unknown, columnType: string, @@ -197,7 +187,8 @@ class EmptyIrisGridModel extends IrisGridModel { bottom: VisibleIndex, columns?: DhType.Column[] ): void { - // No-op + // TODO: move to IrisGridSchemaModelTemplate + this.dispatchEvent(new EventShimCustomEvent(IrisGridModel.EVENT.UPDATED)); } snapshot(ranges: readonly GridRange[]): Promise { diff --git a/packages/iris-grid/src/IrisGridPartitionedTableModel.ts b/packages/iris-grid/src/IrisGridPartitionedTableModel.ts index b21a281c1..ebf83b72b 100644 --- a/packages/iris-grid/src/IrisGridPartitionedTableModel.ts +++ b/packages/iris-grid/src/IrisGridPartitionedTableModel.ts @@ -1,15 +1,15 @@ +/* eslint-disable no-underscore-dangle */ /* eslint class-methods-use-this: "off" */ import type { dh as DhType } from '@deephaven/jsapi-types'; import { Formatter } from '@deephaven/jsapi-utils'; -import { ColumnName } from './CommonTypes'; -import EmptyIrisGridModel from './EmptyIrisGridModel'; +import IrisGridSchemaModelTemplate from './IrisGridSchemaModelTemplate'; import MissingPartitionError, { isMissingPartitionError, } from './MissingPartitionError'; import { PartitionedGridModelProvider } from './PartitionedGridModel'; class IrisGridPartitionedTableModel - extends EmptyIrisGridModel + extends IrisGridSchemaModelTemplate implements PartitionedGridModelProvider { readonly partitionedTable: DhType.PartitionedTable; @@ -24,7 +24,7 @@ class IrisGridPartitionedTableModel partitionedTable: DhType.PartitionedTable, formatter = new Formatter(dh) ) { - super(dh, formatter); + super(dh, partitionedTable.columns, formatter); this.partitionedTable = partitionedTable; } @@ -36,14 +36,6 @@ class IrisGridPartitionedTableModel return false; } - displayString( - value: unknown, - columnType: string, - columnName?: ColumnName - ): string { - return ''; - } - close(): void { this.partitionedTable.close(); } diff --git a/packages/iris-grid/src/IrisGridProxyModel.ts b/packages/iris-grid/src/IrisGridProxyModel.ts index daf73be30..a8eb0ca33 100644 --- a/packages/iris-grid/src/IrisGridProxyModel.ts +++ b/packages/iris-grid/src/IrisGridProxyModel.ts @@ -33,7 +33,6 @@ import { PartitionedGridModel, isPartitionedGridModelProvider, } from './PartitionedGridModel'; -import IrisGridEmptyTableModel from './IrisGridEmptyTableModel'; const log = Log.module('IrisGridProxyModel'); @@ -533,25 +532,15 @@ class IrisGridProxyModel extends IrisGridModel implements PartitionedGridModel { .partitionMergedTable() .then(table => makeModel(this.dh, table, this.formatter)); } else if ( - partitionConfig.mode === 'partition' && - partitionConfig.partitions.length === 0 + partitionConfig.mode !== 'partition' || + partitionConfig.partitions.length > 0 ) { - // originalModel.partitionTable([]) would throw an error - // Return an empty model instead - modelPromise = this.originalModel - // .partitionMergedTable() is a workaround to get a table with the needed schema. - // It will not be expensive since there is no partitions. - // TODO: - // Can we get a table without `partitionMergedTable`? - .partitionMergedTable() - .then( - table => new IrisGridEmptyTableModel(this.dh, table, this.formatter) - ); - } else { modelPromise = this.originalModel .partitionTable(partitionConfig.partitions) .then(table => makeModel(this.dh, table, this.formatter)); } + // If partitionConfig.mode === 'partition' && partitionConfig.partitions.length === 0 + // we keep the original model to show an empty table } this.setNextModel(modelPromise); diff --git a/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts b/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts new file mode 100644 index 000000000..40e8c35e0 --- /dev/null +++ b/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts @@ -0,0 +1,660 @@ +/* eslint-disable no-underscore-dangle */ +/* eslint class-methods-use-this: "off" */ +import memoize from 'memoize-one'; +import { + GridRange, + GridUtils, + ModelIndex, + MoveOperation, + VisibleIndex, +} from '@deephaven/grid'; +import type { dh as DhType } from '@deephaven/jsapi-types'; +import Log from '@deephaven/log'; +import { EMPTY_ARRAY, EMPTY_MAP, EventShimCustomEvent } from '@deephaven/utils'; +import { Formatter } from '@deephaven/jsapi-utils'; +import IrisGridModel, { DisplayColumn } from './IrisGridModel'; +import IrisGridUtils from './IrisGridUtils'; +import { + ColumnName, + UITotalsTableConfig, + PendingDataMap, + CellData, + PendingDataErrorMap, +} from './CommonTypes'; +import ColumnHeaderGroup, { isColumnHeaderGroup } from './ColumnHeaderGroup'; + +const log = Log.module('IrisGridSchemaModelTemplate'); + +/** + * Template model for a grid with a schema and no rows + */ + +class IrisGridSchemaModelTemplate extends IrisGridModel { + private modelFormatter: Formatter; + + private _columns: DhType.Column[]; + + private _columnHeaderGroupMap: Map = new Map(); + + private columnHeaderParentMap: Map = new Map(); + + private _columnHeaderMaxDepth: number | null = null; + + private _columnHeaderGroups: ColumnHeaderGroup[] = []; + + private _movedColumns: MoveOperation[] | null = null; + + /** + * @param dh JSAPI instance + * @param columns Columns array to be used in the model + * @param formatter The formatter to use when getting formats + * @param inputTable Iris input table associated with this table + */ + constructor( + dh: typeof DhType, + columns: DhType.Column[], + formatter = new Formatter(dh), + inputTable: DhType.InputTable | null = null + ) { + super(dh); + + // TODO: + this.handleCustomColumnsChanged = + this.handleCustomColumnsChanged.bind(this); + + // this.dh = dh; + this.modelFormatter = formatter; + // this.irisGridUtils = new IrisGridUtils(dh); + // this.inputTable = inputTable; + // this.subscription = null; + this._columns = columns; + // this.totalsTable = null; + // this.totalsTablePromise = null; + // this.totals = null; + // this.totalsDataMap = null; + + // Map from new row index to their values. Only for input tables that can have new rows added. + // The index of these rows start at 0, and they are appended at the end of the regular table data. + // These rows can be sparse, so using a map instead of an array. + // this.pendingNewDataMap = new Map(); + // this.pendingNewRowCount = 0; + + this.columnHeaderGroups = IrisGridUtils.parseColumnHeaderGroups( + this, + this.layoutHints?.columnGroups ?? [] + ).groups; + } + + get rowCount(): number { + return 0; + } + + get columnCount(): number { + return this.columns.length; + } + + textForCell(column: number, row: number): string { + return ''; + } + + textForColumnHeader(x: ModelIndex, depth = 0): string | undefined { + const header = this.columnAtDepth(x, depth); + if (isColumnHeaderGroup(header)) { + return header.isNew ? '' : header.name; + } + return header?.displayName ?? header?.name; + } + + /** + * Returns an array of the columns in the model + * The order of model columns should never change once established + */ + get columns(): readonly DhType.Column[] { + return this._columns; + } + + /** + * Use this as the canonical column index since things like layoutHints could have + * changed the column order. + */ + getColumnIndexByName(name: ColumnName): number | undefined { + return this.getColumnIndicesByNameMap(this.columns).get(name); + } + + getColumnIndicesByNameMap = memoize( + (columns: readonly DhType.Column[]): Map => { + const indices = new Map(); + columns.forEach(({ name }, i) => indices.set(name, i)); + return indices; + } + ); + + get initialMovedRows(): readonly MoveOperation[] { + return EMPTY_ARRAY; + } + + formatForCell( + column: ModelIndex, + row: ModelIndex + ): DhType.Format | undefined { + return undefined; + } + + valueForCell(column: ModelIndex, row: ModelIndex): unknown { + return undefined; + } + + get filter(): readonly DhType.FilterCondition[] { + // TODO: retrieve from this._filter? + return EMPTY_ARRAY; + } + + set filter(filter: readonly DhType.FilterCondition[]) { + // No-op + } + + get partition(): readonly unknown[] { + // TODO: retrieve from this._partition? + return EMPTY_ARRAY; + } + + set partition(partition: readonly unknown[]) { + // No-op + } + + get partitionColumns(): readonly DhType.Column[] { + return EMPTY_ARRAY; + } + + get formatter(): Formatter { + return this.modelFormatter; + } + + set formatter(formatter: Formatter) { + this.modelFormatter = formatter; + } + + displayString( + value: unknown, + columnType: string, + columnName?: ColumnName + ): string { + return ''; + } + + get sort(): readonly DhType.Sort[] { + return EMPTY_ARRAY; + } + + set sort(sort: readonly DhType.Sort[]) { + // No-op + } + + get customColumns(): readonly ColumnName[] { + return EMPTY_ARRAY; + } + + set customColumns(customColumns: readonly ColumnName[]) { + // No-op + } + + get formatColumns(): readonly DhType.CustomColumn[] { + return EMPTY_ARRAY; + } + + updateFrozenColumns(columns: readonly ColumnName[]): void { + // Do nothing + } + + get rollupConfig(): DhType.RollupConfig | null { + return null; + } + + set rollupConfig(rollupConfig: DhType.RollupConfig | null) { + // No-op + } + + get totalsConfig(): UITotalsTableConfig | null { + return null; + } + + set totalsConfig(totalsConfig: UITotalsTableConfig | null) { + // No-op + } + + export(): Promise { + throw new Error('Method not implemented.'); + } + + columnStatistics(column: DhType.Column): Promise { + throw new Error('Method not implemented.'); + } + + get selectDistinctColumns(): readonly ColumnName[] { + return EMPTY_ARRAY; + } + + set selectDistinctColumns(selectDistinctColumns: readonly ColumnName[]) { + // No-op + } + + get pendingDataMap(): PendingDataMap { + return EMPTY_MAP; + } + + set pendingDataMap(map: PendingDataMap) { + // No-op + } + + get pendingRowCount(): number { + return 0; + } + + set pendingRowCount(count: number) { + // No-op + } + + get pendingDataErrors(): PendingDataErrorMap { + return EMPTY_MAP; + } + + commitPending(): Promise { + return Promise.resolve(); + } + + setViewport( + top: VisibleIndex, + bottom: VisibleIndex, + columns?: DhType.Column[] + ): void { + // TODO: move to IrisGridSchemaModelTemplate + this.dispatchEvent(new EventShimCustomEvent(IrisGridModel.EVENT.UPDATED)); + } + + snapshot(ranges: readonly GridRange[]): Promise { + return Promise.resolve([]); + } + + textSnapshot( + ranges: readonly GridRange[], + includeHeaders?: boolean, + formatValue?: ( + value: unknown, + column: DhType.Column, + row?: DhType.Row + ) => string + ): Promise { + return Promise.resolve(''); + } + + valuesTable( + columns: DhType.Column | readonly DhType.Column[] + ): Promise { + throw new Error('Method not implemented.'); + } + + delete(ranges: readonly GridRange[]): Promise { + return Promise.resolve(); + } + + seekRow( + startRow: number, + column: DhType.Column, + valueType: unknown, + value: unknown, + insensitive?: boolean, + contains?: boolean, + isBackwards?: boolean + ): Promise { + return Promise.resolve(0); + } + + close(): void { + // No-op + } + + handleCustomColumnsChanged(): void { + // TODO: dispatch event when setting custom columns + this.dispatchEvent( + new EventShimCustomEvent(IrisGridModel.EVENT.COLUMNS_CHANGED, { + detail: this.columns, + }) + ); + } + + get maxPendingDataRow(): number { + return 0; + } + + get floatingBottomRowCount(): number { + return 0; + } + + get floatingTopRowCount(): number { + return 0; + } + + get isEditable(): boolean { + return false; + } + + colorForColumnHeader(x: ModelIndex, depth = 0): string | null { + const column = this.columnAtDepth(x, depth); + if (isColumnHeaderGroup(column)) { + return column.color ?? null; + } + return null; + } + + getColumnHeaderGroup( + modelIndex: ModelIndex, + depth: number + ): ColumnHeaderGroup | undefined { + const group = this.columnAtDepth(modelIndex, depth); + if (isColumnHeaderGroup(group)) { + return group; + } + return undefined; + } + + getColumnHeaderParentGroup( + modelIndex: ModelIndex, + depth: number + ): ColumnHeaderGroup | undefined { + return this.columnHeaderParentMap.get( + this.columnAtDepth(modelIndex, depth)?.name ?? '' + ); + } + + columnAtDepth( + x: ModelIndex, + depth = 0 + ): ColumnHeaderGroup | DisplayColumn | undefined { + if (depth === 0) { + return this.columns[x]; + } + + const columnName = this.columns[x]?.name; + let group = this.columnHeaderParentMap.get(columnName); + + if (!group) { + return undefined; + } + + let currentDepth = group.depth; + while (currentDepth < depth) { + group = this.columnHeaderParentMap.get(group.name); + if (!group) { + return undefined; + } + currentDepth = group.depth; + } + + if (group.depth === depth) { + return group; + } + + return undefined; + } + + private getMemoizedInitialMovedColumns = memoize( + (layoutHints?: DhType.LayoutHints): readonly MoveOperation[] => { + if (!layoutHints) { + return EMPTY_ARRAY; + } + let movedColumns: MoveOperation[] = []; + const { groupMap } = IrisGridUtils.parseColumnHeaderGroups( + this, + layoutHints?.columnGroups ?? [] + ); + + const moveColumn = (name: string, toIndex: VisibleIndex): void => { + const modelIndex = this.getColumnIndexByName(name); + if (modelIndex == null) { + throw new Error(`Unknown layout hint column: ${name}`); + } + const visibleIndex = GridUtils.getVisibleIndex( + modelIndex, + movedColumns + ); + movedColumns = GridUtils.moveItem(visibleIndex, toIndex, movedColumns); + }; + + const moveGroup = (name: string, toIndex: VisibleIndex): void => { + const group = groupMap.get(name); + if (group == null) { + throw new Error(`Unknown layout hint group: ${name}`); + } + const visibleRange = group.getVisibleRange(movedColumns); + movedColumns = GridUtils.moveRange(visibleRange, toIndex, movedColumns); + }; + + const frontColumns = layoutHints.frontColumns ?? []; + const backColumns = layoutHints.backColumns ?? []; + const frozenColumns = layoutHints.frozenColumns ?? []; + + if ( + frontColumns.length > 0 || + backColumns.length > 0 || + frozenColumns.length > 0 + ) { + const usedColumns = new Set(); + + let frontIndex = 0; + frozenColumns?.forEach(name => { + if (usedColumns.has(name)) { + throw new Error( + `Column specified in multiple layout hints: ${name}` + ); + } + moveColumn(name, frontIndex); + frontIndex += 1; + }); + frontColumns.forEach(name => { + if (usedColumns.has(name)) { + throw new Error( + `Column specified in multiple layout hints: ${name}` + ); + } + moveColumn(name, frontIndex); + frontIndex += 1; + }); + + let backIndex = this.columnMap.size - 1; + backColumns?.forEach(name => { + if (usedColumns.has(name)) { + throw new Error( + `Column specified in multiple layout hints: ${name}` + ); + } + moveColumn(name, backIndex); + backIndex -= 1; + }); + } + + const layoutHintColumnGroups = layoutHints?.columnGroups; + if (layoutHintColumnGroups) { + const columnGroups = [...groupMap.values()]; + columnGroups.sort((a, b) => a.depth - b.depth); + + columnGroups.forEach(group => { + const firstChildName = group.children[0]; + const rightModelIndex = this.getColumnIndexByName(firstChildName); + + let rightVisibleIndex: number; + + if (rightModelIndex != null) { + rightVisibleIndex = GridUtils.getVisibleIndex( + rightModelIndex, + movedColumns + ); + } else { + const firstChildGroup = groupMap.get(firstChildName); + if (!firstChildGroup) { + throw new Error( + `Unknown column ${firstChildName} in group ${group.name}` + ); + } + + const visibleRange = firstChildGroup.getVisibleRange(movedColumns); + // Columns will be moved to start at the end of the first child group + [, rightVisibleIndex] = visibleRange; + } + + for (let i = 1; i < group.children.length; i += 1) { + const childName = group.children[i]; + const childGroup = groupMap.get(childName); + const childColumn = this.getColumnIndexByName(childName); + + if (childGroup) { + // All columns in the group will be before or after the start index + // Lower level groups are re-arranged first, so they will be contiguous + const isBeforeGroup = + childGroup.getVisibleRange(movedColumns)[0] < rightVisibleIndex; + + const moveToIndex = isBeforeGroup + ? rightVisibleIndex - childGroup.childIndexes.length + 1 + : rightVisibleIndex + 1; + + moveGroup(childName, moveToIndex); + rightVisibleIndex = + moveToIndex + childGroup.childIndexes.length - 1; + } else if (childColumn != null) { + const isBeforeGroup = + GridUtils.getVisibleIndex(childColumn, movedColumns) < + rightVisibleIndex; + + const moveToIndex = isBeforeGroup + ? rightVisibleIndex + : rightVisibleIndex + 1; + moveColumn(childName, moveToIndex); + rightVisibleIndex = moveToIndex; + } + } + }); + } + + this._movedColumns = movedColumns; + return movedColumns; + } + ); + + /** + * Used to get the initial moved columns based on layout hints + */ + get initialMovedColumns(): readonly MoveOperation[] { + return this.getMemoizedInitialMovedColumns(this.layoutHints ?? undefined); + } + + getMemoizedInitialColumnHeaderGroups = memoize( + (layoutHints?: DhType.LayoutHints) => + IrisGridUtils.parseColumnHeaderGroups( + this, + layoutHints?.columnGroups ?? [] + ).groups + ); + + get initialColumnHeaderGroups(): readonly ColumnHeaderGroup[] { + return this.getMemoizedInitialColumnHeaderGroups( + this.layoutHints ?? undefined + ); + } + + getMemoizedColumnMap = memoize( + (tableColumns: readonly DhType.Column[]): Map => { + const columnMap = new Map(); + tableColumns.forEach(col => columnMap.set(col.name, col)); + return columnMap; + } + ); + + get columnMap(): Map { + return this.getMemoizedColumnMap(this.columns); + } + + get columnHeaderMaxDepth(): number { + return this._columnHeaderMaxDepth ?? 1; + } + + private set columnHeaderMaxDepth(depth: number) { + this._columnHeaderMaxDepth = depth; + } + + get columnHeaderGroupMap(): ReadonlyMap { + return this._columnHeaderGroupMap; + } + + get columnHeaderGroups(): readonly ColumnHeaderGroup[] { + return this._columnHeaderGroups; + } + + set columnHeaderGroups(groups: readonly ColumnHeaderGroup[]) { + if (groups === this._columnHeaderGroups) { + return; + } + + const { + groups: newGroups, + maxDepth, + parentMap, + groupMap, + } = IrisGridUtils.parseColumnHeaderGroups( + this, + groups ?? this.initialColumnHeaderGroups + ); + + this._columnHeaderGroups = newGroups; + this.columnHeaderMaxDepth = maxDepth; + this.columnHeaderParentMap = parentMap; + this._columnHeaderGroupMap = groupMap; + } + + /** + * Check if a row is a pending input row + * @param y The row in the model to check if it's a pending new row + * @returns True if the row is a pending new row, false if not + */ + isPendingRow(y: ModelIndex): boolean { + return false; + } + + dataForCell(x: ModelIndex, y: ModelIndex): CellData | undefined { + return undefined; + } + + isColumnMovable(modelIndex: ModelIndex, depth: number): boolean { + if (modelIndex < 0 || modelIndex >= this.columnCount) { + return false; + } + + // All groups are movable + if (depth > 0) { + return true; + } + + const columnName = this.columns[modelIndex].name; + if ( + this.frontColumns.includes(columnName) || + this.backColumns.includes(columnName) || + this.frozenColumns.includes(columnName) || + !columnName + ) { + return false; + } + return !this.isKeyColumn(modelIndex); + } + + isColumnSortable(modelIndex: ModelIndex): boolean { + return this.columns[modelIndex].isSortable ?? true; + } + + isKeyColumn(x: ModelIndex): boolean { + return false; + } + + isRowMovable(): boolean { + return false; + } +} + +export default IrisGridSchemaModelTemplate; From 734e2626d2ae14a5f97b3670c4b98aa002dac512 Mon Sep 17 00:00:00 2001 From: Vlad Babich Date: Fri, 10 May 2024 08:52:49 -0600 Subject: [PATCH 5/9] Subscribe to partition table updates --- packages/iris-grid/src/IrisGrid.tsx | 168 ++++++++++++------ .../src/IrisGridPartitionSelector.tsx | 16 +- .../src/IrisGridSchemaModelTemplate.ts | 24 +-- 3 files changed, 131 insertions(+), 77 deletions(-) diff --git a/packages/iris-grid/src/IrisGrid.tsx b/packages/iris-grid/src/IrisGrid.tsx index de7a7f472..46ffdbab9 100644 --- a/packages/iris-grid/src/IrisGrid.tsx +++ b/packages/iris-grid/src/IrisGrid.tsx @@ -148,7 +148,6 @@ import IrisGridModel from './IrisGridModel'; import { isPartitionedGridModel, PartitionConfig, - PartitionedGridModel, } from './PartitionedGridModel'; import IrisGridPartitionSelector from './IrisGridPartitionSelector'; import SelectDistinctBuilder from './sidebar/SelectDistinctBuilder'; @@ -357,6 +356,7 @@ export interface IrisGridState { metricCalculator: IrisGridMetricCalculator; metrics?: GridMetrics; + keyTable?: DhType.Table; partitionConfig?: PartitionConfig; // setAdvancedFilter and setQuickFilter mutate the arguments @@ -597,6 +597,8 @@ class IrisGrid extends Component { this.handleDownloadCanceled = this.handleDownloadCanceled.bind(this); this.handleDownloadCompleted = this.handleDownloadCompleted.bind(this); this.handlePartitionChange = this.handlePartitionChange.bind(this); + this.handlePartitionTableUpdate = + this.handlePartitionTableUpdate.bind(this); this.handleColumnVisibilityChanged = this.handleColumnVisibilityChanged.bind(this); this.handleColumnVisibilityReset = @@ -775,6 +777,7 @@ class IrisGrid extends Component { metricCalculator, metrics: undefined, + keyTable: undefined, partitionConfig: partitionConfig ?? (partitions && partitions.length @@ -872,6 +875,7 @@ class IrisGrid extends Component { const { model } = this.props; this.initState(); this.startListening(model); + this.startListeningForPartitionChanges(model); } componentDidUpdate(prevProps: IrisGridProps, prevState: IrisGridState): void { @@ -884,9 +888,17 @@ class IrisGrid extends Component { sorts, } = this.props; + const { isSelectingPartition } = this.state; + + if (isSelectingPartition && !prevState.isSelectingPartition) { + this.loadTableState(); + } + if (model !== prevProps.model) { + this.stopListeningForPartitionChanges(prevProps.model, true); this.stopListening(prevProps.model); this.startListening(model); + this.startListeningForPartitionChanges(model); } const changedInputFilters = @@ -961,6 +973,7 @@ class IrisGrid extends Component { componentWillUnmount(): void { const { model } = this.props; + this.stopListeningForPartitionChanges(model); this.stopListening(model); this.pending.cancel(); this.updateSearchFilter.cancel(); @@ -1958,10 +1971,11 @@ class IrisGrid extends Component { } initState(): void { - const { model } = this.props; try { + const { model } = this.props; if (isPartitionedGridModel(model) && model.isPartitionRequired) { - this.loadPartitionsTable(model); + // Wait for the partition table to load + log.debug('Loading partition table'); } else { this.loadTableState(); } @@ -1971,6 +1985,7 @@ class IrisGrid extends Component { } loadTableState(): void { + log.debug('Loading table state'); const { applyInputFiltersOnInit, inputFilters, @@ -2011,58 +2026,33 @@ class IrisGrid extends Component { this.initFormatter(); } - async loadPartitionsTable(model: PartitionedGridModel): Promise { - try { - const partitionConfig = await this.getInitialPartitionConfig(model); - this.setState( - { isSelectingPartition: true, partitionConfig }, - this.loadTableState - ); - } catch (error) { - if (!PromiseUtils.isCanceled(error)) { - this.handleTableLoadError(error); - } + handlePartitionTableUpdate(event: CustomEvent): void { + const { keyTable } = this.state; + assertNotNull(keyTable); + const { detail: data } = event; + let values: unknown[] = []; + if (data.rows.length > 0) { + const row = data.rows[0]; + values = keyTable.columns.map(column => row.get(column)); + // TODO: test the case with deleting partitions, and ticking in and out, etc } - } - - /** - * Gets the initial partition config for the currently set model. - * Sorts the key table and gets the first key. - * If the table is ticking, it will wait for the first tick. - */ - async getInitialPartitionConfig( - model: PartitionedGridModel - ): Promise { - const { partitionConfig } = this.state; - if (partitionConfig !== undefined) { - // User already has a partition selected, just use that - return partitionConfig; - } - - const keyTable = await this.pending.add( - model.partitionKeysTable(), - resolved => resolved.close() - ); - - const sorts = keyTable.columns.map(column => column.sort().desc()); - keyTable.applySort(sorts); - keyTable.setViewport(0, 0); - - const { rows } = await keyTable.getViewportData(); - let values = []; - if (rows.length > 0) { - const row = rows[0]; - // Core JSAPI returns undefined for null table values, - // coalesce with null to differentiate null from no selection in the dropdown - // https://github.com/deephaven/deephaven-core/issues/5400 - values = keyTable.columns.map(column => row.get(column) ?? null); - } - const newPartition: PartitionConfig = { - partitions: values, - mode: 'partition', - }; - keyTable.close(); - return newPartition; + log.debug2('Partition table update', values); + this.setState(state => { + const { partitions = [] } = state.partitionConfig ?? {}; + if ( + (partitions.length > 0 && values.length > 0) || + (partitions.length === 0 && values.length === 0) + ) { + // Partitions already set, ignore update + return null; + } + // Added first partition, or removed last - change mode + const partitionConfig: PartitionConfig = { + mode: 'partition', + partitions: values, + }; + return { partitionConfig }; + }); } copyCell( @@ -2287,6 +2277,71 @@ class IrisGrid extends Component { ); } + async startListeningForPartitionChanges(model: IrisGridModel): Promise { + log.debug('startListeningForPartitionChanges'); + if (!isPartitionedGridModel(model) || !model.isPartitionRequired) { + return; + } + const { partitionConfig } = this.state; + if (partitionConfig !== undefined) { + // User already has a partition selected, just use that + return; + } + + this.setState({ + isSelectingPartition: true, + partitionConfig: { + mode: 'partition', + partitions: [], + }, + }); + + log.debug('startListeningForPartitionChanges before keyTable'); + const keyTable = await this.pending.add( + model.partitionKeysTable(), + resolved => resolved.close() + ); + log.debug( + 'startListeningForPartitionChanges after keyTable', + keyTable, + keyTable.size + ); + const { dh } = model; + + // TODO: move isSelectingPartition higher? + // TODO: set initial partitionConfig to empty? + this.setState({ keyTable }); + + keyTable.addEventListener( + dh.Table.EVENT_UPDATED, + this.handlePartitionTableUpdate + ); + + const sorts = keyTable.columns.map(column => column.sort().desc()); + keyTable.applySort(sorts); + // Select the last partition by default + keyTable.setViewport(0, 0); + } + + stopListeningForPartitionChanges( + model: IrisGridModel, + resetState = false + ): void { + const { keyTable } = this.state; + const { dh } = model; + + keyTable?.removeEventListener( + dh.Table.EVENT_UPDATED, + this.handlePartitionTableUpdate + ); + + keyTable?.close(); + + if (resetState) { + this.setState({ keyTable: undefined }); + } + } + focus(): void { this.grid?.focus(); } @@ -3021,12 +3076,13 @@ class IrisGrid extends Component { const { detail: error } = event as CustomEvent; log.error('request failed:', error); this.stopLoading(); - const { partitionConfig } = this.state; + const { partitionConfig, keyTable } = this.state; if (isMissingPartitionError(error) && partitionConfig != null) { // We'll try loading the initial partition again this.startLoading('Reloading partition...', { resetRanges: true }); this.setState({ partitionConfig: undefined }, () => { - this.initState(); + // Trigger EVENT_UPDATED + keyTable?.setViewport(0, 0); }); } else if (this.canRollback()) { this.startLoading('Rolling back changes...', { resetRanges: true }); diff --git a/packages/iris-grid/src/IrisGridPartitionSelector.tsx b/packages/iris-grid/src/IrisGridPartitionSelector.tsx index cd7df309f..6a1fe04b9 100644 --- a/packages/iris-grid/src/IrisGridPartitionSelector.tsx +++ b/packages/iris-grid/src/IrisGridPartitionSelector.tsx @@ -68,6 +68,8 @@ class IrisGridPartitionSelector extends Component< t => t.close() ); + // TODO: find out how partitionTables can be null after this + const partitionTables = await Promise.all( model.partitionColumns.map(async (_, i) => this.pending.add( @@ -232,7 +234,12 @@ class IrisGridPartitionSelector extends Component< */ updatePartitionFilters(): void { const { partitionTables } = this.state; - assertNotNull(partitionTables); + // assertNotNull(partitionTables); + + if (partitionTables == null) { + this.setState({ partitionFilters: [] }); + return; + } const { partitionConfig } = this.props; const { mode } = partitionConfig; @@ -253,8 +260,11 @@ class IrisGridPartitionSelector extends Component< log.debug('getPartitionFilters', partitionConfig); if (partitions.length !== partitionTables.length) { - throw new Error( - `Invalid partition config set. Expected ${partitionTables.length} partitions, but got ${partitions.length}` + // throw new Error( + // `Invalid partition config set. Expected ${partitionTables.length} partitions, but got ${partitions.length}` + // ); + log.warn( + `Expected ${partitionTables.length} partitions, but got ${partitions.length}` ); } diff --git a/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts b/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts index 40e8c35e0..11eb58a1d 100644 --- a/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts +++ b/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts @@ -23,8 +23,6 @@ import { } from './CommonTypes'; import ColumnHeaderGroup, { isColumnHeaderGroup } from './ColumnHeaderGroup'; -const log = Log.module('IrisGridSchemaModelTemplate'); - /** * Template model for a grid with a schema and no rows */ @@ -62,23 +60,8 @@ class IrisGridSchemaModelTemplate extends IrisGridModel { this.handleCustomColumnsChanged = this.handleCustomColumnsChanged.bind(this); - // this.dh = dh; this.modelFormatter = formatter; - // this.irisGridUtils = new IrisGridUtils(dh); - // this.inputTable = inputTable; - // this.subscription = null; this._columns = columns; - // this.totalsTable = null; - // this.totalsTablePromise = null; - // this.totals = null; - // this.totalsDataMap = null; - - // Map from new row index to their values. Only for input tables that can have new rows added. - // The index of these rows start at 0, and they are appended at the end of the regular table data. - // These rows can be sparse, so using a map instead of an array. - // this.pendingNewDataMap = new Map(); - // this.pendingNewRowCount = 0; - this.columnHeaderGroups = IrisGridUtils.parseColumnHeaderGroups( this, this.layoutHints?.columnGroups ?? [] @@ -203,7 +186,7 @@ class IrisGridSchemaModelTemplate extends IrisGridModel { } updateFrozenColumns(columns: readonly ColumnName[]): void { - // Do nothing + // No-op } get rollupConfig(): DhType.RollupConfig | null { @@ -655,6 +638,11 @@ class IrisGridSchemaModelTemplate extends IrisGridModel { isRowMovable(): boolean { return false; } + + /* IrisGridTableModel */ + get isSeekRowAvailable(): boolean { + return false; + } } export default IrisGridSchemaModelTemplate; From add2766acd591e71219367c9a88d5698e2edf6b2 Mon Sep 17 00:00:00 2001 From: Vlad Babich Date: Fri, 10 May 2024 08:55:56 -0600 Subject: [PATCH 6/9] Delete comments --- packages/iris-grid/src/IrisGrid.tsx | 3 --- .../src/IrisGridSchemaModelTemplate.ts | 23 ++----------------- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/packages/iris-grid/src/IrisGrid.tsx b/packages/iris-grid/src/IrisGrid.tsx index 46ffdbab9..7525dcfd1 100644 --- a/packages/iris-grid/src/IrisGrid.tsx +++ b/packages/iris-grid/src/IrisGrid.tsx @@ -2034,7 +2034,6 @@ class IrisGrid extends Component { if (data.rows.length > 0) { const row = data.rows[0]; values = keyTable.columns.map(column => row.get(column)); - // TODO: test the case with deleting partitions, and ticking in and out, etc } log.debug2('Partition table update', values); this.setState(state => { @@ -2308,8 +2307,6 @@ class IrisGrid extends Component { ); const { dh } = model; - // TODO: move isSelectingPartition higher? - // TODO: set initial partitionConfig to empty? this.setState({ keyTable }); keyTable.addEventListener( diff --git a/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts b/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts index 11eb58a1d..5ce95a708 100644 --- a/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts +++ b/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts @@ -46,20 +46,14 @@ class IrisGridSchemaModelTemplate extends IrisGridModel { * @param dh JSAPI instance * @param columns Columns array to be used in the model * @param formatter The formatter to use when getting formats - * @param inputTable Iris input table associated with this table */ constructor( dh: typeof DhType, columns: DhType.Column[], - formatter = new Formatter(dh), - inputTable: DhType.InputTable | null = null + formatter = new Formatter(dh) ) { super(dh); - // TODO: - this.handleCustomColumnsChanged = - this.handleCustomColumnsChanged.bind(this); - this.modelFormatter = formatter; this._columns = columns; this.columnHeaderGroups = IrisGridUtils.parseColumnHeaderGroups( @@ -250,7 +244,6 @@ class IrisGridSchemaModelTemplate extends IrisGridModel { bottom: VisibleIndex, columns?: DhType.Column[] ): void { - // TODO: move to IrisGridSchemaModelTemplate this.dispatchEvent(new EventShimCustomEvent(IrisGridModel.EVENT.UPDATED)); } @@ -296,15 +289,6 @@ class IrisGridSchemaModelTemplate extends IrisGridModel { // No-op } - handleCustomColumnsChanged(): void { - // TODO: dispatch event when setting custom columns - this.dispatchEvent( - new EventShimCustomEvent(IrisGridModel.EVENT.COLUMNS_CHANGED, { - detail: this.columns, - }) - ); - } - get maxPendingDataRow(): number { return 0; } @@ -639,10 +623,7 @@ class IrisGridSchemaModelTemplate extends IrisGridModel { return false; } - /* IrisGridTableModel */ - get isSeekRowAvailable(): boolean { - return false; - } + // TODO: Add frozen columns, isColumnMoveable, isFilterable, etc. from IrisGridModel } export default IrisGridSchemaModelTemplate; From 1d5d18df61f6174bf6a15abbc8cf0db6283a2cb1 Mon Sep 17 00:00:00 2001 From: Vlad Babich Date: Fri, 10 May 2024 09:04:41 -0600 Subject: [PATCH 7/9] Fix unit tests --- packages/iris-grid/src/IrisGridSchemaModelTemplate.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts b/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts index 5ce95a708..739b1a789 100644 --- a/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts +++ b/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts @@ -9,7 +9,6 @@ import { VisibleIndex, } from '@deephaven/grid'; import type { dh as DhType } from '@deephaven/jsapi-types'; -import Log from '@deephaven/log'; import { EMPTY_ARRAY, EMPTY_MAP, EventShimCustomEvent } from '@deephaven/utils'; import { Formatter } from '@deephaven/jsapi-utils'; import IrisGridModel, { DisplayColumn } from './IrisGridModel'; From 197dfd49043d1a5a185d1b7c4969e15b9a97f30d Mon Sep 17 00:00:00 2001 From: Vlad Babich Date: Mon, 13 May 2024 08:54:02 -0600 Subject: [PATCH 8/9] Clean up EmptyIrisGridModel --- packages/iris-grid/src/EmptyIrisGridModel.ts | 115 +----------------- .../iris-grid/src/IrisGridEmptyTableModel.ts | 26 ---- .../src/IrisGridSchemaModelTemplate.ts | 2 +- 3 files changed, 4 insertions(+), 139 deletions(-) delete mode 100644 packages/iris-grid/src/IrisGridEmptyTableModel.ts diff --git a/packages/iris-grid/src/EmptyIrisGridModel.ts b/packages/iris-grid/src/EmptyIrisGridModel.ts index 00e46588b..1f2ab4b6d 100644 --- a/packages/iris-grid/src/EmptyIrisGridModel.ts +++ b/packages/iris-grid/src/EmptyIrisGridModel.ts @@ -1,40 +1,21 @@ /* eslint class-methods-use-this: "off" */ -import { - GridRange, - ModelIndex, - MoveOperation, - VisibleIndex, -} from '@deephaven/grid'; +import { ModelIndex, MoveOperation } from '@deephaven/grid'; import { dh as DhType } from '@deephaven/jsapi-types'; import { ColumnName, Formatter } from '@deephaven/jsapi-utils'; -import { EMPTY_ARRAY, EMPTY_MAP, EventShimCustomEvent } from '@deephaven/utils'; -import IrisGridModel from './IrisGridModel'; +import { EMPTY_ARRAY, EMPTY_MAP } from '@deephaven/utils'; import ColumnHeaderGroup from './ColumnHeaderGroup'; -import { - PendingDataErrorMap, - PendingDataMap, - UITotalsTableConfig, -} from './CommonTypes'; +import { UITotalsTableConfig } from './CommonTypes'; import IrisGridSchemaModelTemplate from './IrisGridSchemaModelTemplate'; -// TODO: delete all methods overlapping with IrisGridSchemaModelTemplate class EmptyIrisGridModel extends IrisGridSchemaModelTemplate { constructor(dh: typeof DhType, formatter = new Formatter(dh)) { super(dh, []); } - get rowCount(): number { - return 0; - } - get columnCount(): number { return 0; } - textForCell(column: number, row: number): string { - return ''; - } - textForColumnHeader(column: ModelIndex, depth?: number): string | undefined { return undefined; } @@ -63,17 +44,6 @@ class EmptyIrisGridModel extends IrisGridSchemaModelTemplate { return EMPTY_ARRAY; } - formatForCell( - column: ModelIndex, - row: ModelIndex - ): DhType.Format | undefined { - return undefined; - } - - valueForCell(column: ModelIndex, row: ModelIndex): unknown { - return undefined; - } - get filter(): readonly DhType.FilterCondition[] { return EMPTY_ARRAY; } @@ -94,14 +64,6 @@ class EmptyIrisGridModel extends IrisGridSchemaModelTemplate { return EMPTY_ARRAY; } - displayString( - value: unknown, - columnType: string, - columnName?: ColumnName - ): string { - return ''; - } - get sort(): readonly DhType.Sort[] { return EMPTY_ARRAY; } @@ -158,77 +120,6 @@ class EmptyIrisGridModel extends IrisGridSchemaModelTemplate { // No-op } - get pendingDataMap(): PendingDataMap { - return EMPTY_MAP; - } - - set pendingDataMap(map: PendingDataMap) { - // No-op - } - - get pendingRowCount(): number { - return 0; - } - - set pendingRowCount(count: number) { - // No-op - } - - get pendingDataErrors(): PendingDataErrorMap { - return EMPTY_MAP; - } - - commitPending(): Promise { - return Promise.resolve(); - } - - setViewport( - top: VisibleIndex, - bottom: VisibleIndex, - columns?: DhType.Column[] - ): void { - // TODO: move to IrisGridSchemaModelTemplate - this.dispatchEvent(new EventShimCustomEvent(IrisGridModel.EVENT.UPDATED)); - } - - snapshot(ranges: readonly GridRange[]): Promise { - return Promise.resolve([]); - } - - textSnapshot( - ranges: readonly GridRange[], - includeHeaders?: boolean, - formatValue?: ( - value: unknown, - column: DhType.Column, - row?: DhType.Row - ) => string - ): Promise { - return Promise.resolve(''); - } - - valuesTable( - columns: DhType.Column | readonly DhType.Column[] - ): Promise { - throw new Error('Method not implemented.'); - } - - delete(ranges: readonly GridRange[]): Promise { - return Promise.resolve(); - } - - seekRow( - startRow: number, - column: DhType.Column, - valueType: unknown, - value: unknown, - insensitive?: boolean, - contains?: boolean, - isBackwards?: boolean - ): Promise { - return Promise.resolve(0); - } - get columnHeaderGroups(): readonly ColumnHeaderGroup[] { return EMPTY_ARRAY; } diff --git a/packages/iris-grid/src/IrisGridEmptyTableModel.ts b/packages/iris-grid/src/IrisGridEmptyTableModel.ts deleted file mode 100644 index 14f1aa9e0..000000000 --- a/packages/iris-grid/src/IrisGridEmptyTableModel.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint class-methods-use-this: "off" */ -import type { dh as DhType } from '@deephaven/jsapi-types'; -import { Formatter } from '@deephaven/jsapi-utils'; -import IrisGridTableModel from './IrisGridTableModel'; - -/** Model that represents an empty table with a schema from the provided table */ -class IrisGridEmptyTableModel extends IrisGridTableModel { - /** - * @param dh JSAPI instance - * @param table Table to be used in the model - * @param formatter The formatter to use when getting formats - */ - constructor( - dh: typeof DhType, - table: DhType.Table, - formatter = new Formatter(dh) - ) { - super(dh, table, formatter); - } - - get size(): number { - return 0; - } -} - -export default IrisGridEmptyTableModel; diff --git a/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts b/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts index 739b1a789..3d22d78da 100644 --- a/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts +++ b/packages/iris-grid/src/IrisGridSchemaModelTemplate.ts @@ -247,7 +247,7 @@ class IrisGridSchemaModelTemplate extends IrisGridModel { } snapshot(ranges: readonly GridRange[]): Promise { - return Promise.resolve([]); + return Promise.resolve(EMPTY_ARRAY); } textSnapshot( From 60c8bb70fe75904179c32c29f05545ea18dc7e59 Mon Sep 17 00:00:00 2001 From: Vlad Babich Date: Mon, 13 May 2024 08:56:14 -0600 Subject: [PATCH 9/9] Added TODO --- packages/iris-grid/src/IrisGridPartitionSelector.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/iris-grid/src/IrisGridPartitionSelector.tsx b/packages/iris-grid/src/IrisGridPartitionSelector.tsx index 6a1fe04b9..91cd2f99a 100644 --- a/packages/iris-grid/src/IrisGridPartitionSelector.tsx +++ b/packages/iris-grid/src/IrisGridPartitionSelector.tsx @@ -234,6 +234,7 @@ class IrisGridPartitionSelector extends Component< */ updatePartitionFilters(): void { const { partitionTables } = this.state; + // TODO: cleanup, partitionTables can be null now // assertNotNull(partitionTables); if (partitionTables == null) {