diff --git a/packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx b/packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx index a87c6c876..ff823b527 100644 --- a/packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx @@ -53,6 +53,7 @@ import { type RowDataMap, type AdvancedFilterOptions, type FormattingRule, + type SortDescriptor, } from '@deephaven/jsapi-utils'; import Log from '@deephaven/log'; import { @@ -199,7 +200,7 @@ interface IrisGridPanelState { columnAlignmentMap: Map; isFilterBarShown: boolean; quickFilters: ReadonlyQuickFilterMap; - sorts: readonly dh.Sort[]; + sorts: readonly SortDescriptor[]; userColumnWidths: ModelSizeMap; userRowHeights: ModelSizeMap; reverse: boolean; diff --git a/packages/iris-grid/src/CommonTypes.tsx b/packages/iris-grid/src/CommonTypes.tsx index bb57eee79..a9c20f007 100644 --- a/packages/iris-grid/src/CommonTypes.tsx +++ b/packages/iris-grid/src/CommonTypes.tsx @@ -1,4 +1,7 @@ -import { type AdvancedFilterOptions } from '@deephaven/jsapi-utils'; +import { + type AdvancedFilterOptions, + type SortDescriptor, +} from '@deephaven/jsapi-utils'; import { type GridRangeIndex, type ModelIndex } from '@deephaven/grid'; import type { dh } from '@deephaven/jsapi-types'; import { type Shortcut } from '@deephaven/components'; @@ -97,7 +100,7 @@ export interface IrisGridStateOverride extends Record { loadingScrimProgress: number | null; advancedFilters: ReadonlyAdvancedFilterMap; quickFilters: ReadonlyQuickFilterMap; - sorts: readonly dh.Sort[]; + sorts: readonly SortDescriptor[]; reverse: boolean; rollupConfig: UIRollupConfig | undefined; } diff --git a/packages/iris-grid/src/IrisGrid.tsx b/packages/iris-grid/src/IrisGrid.tsx index 5fe73bde2..af051d98a 100644 --- a/packages/iris-grid/src/IrisGrid.tsx +++ b/packages/iris-grid/src/IrisGrid.tsx @@ -77,6 +77,7 @@ import { type TableColumnFormat, type Settings, isSortDirection, + type SortDescriptor, } from '@deephaven/jsapi-utils'; import { assertNotNull, @@ -237,7 +238,7 @@ function isEmptyConfig({ rollupConfig?: UIRollupConfig; searchFilter?: DhType.FilterCondition; selectDistinctColumns: readonly ColumnName[]; - sorts: readonly DhType.Sort[]; + sorts: readonly SortDescriptor[]; }): boolean { return ( advancedFilters.size === 0 && @@ -312,7 +313,7 @@ export interface IrisGridProps { /** @deprecated use `partitionConfig` instead */ partitions?: (string | null)[]; partitionConfig?: PartitionConfig; - sorts: readonly DhType.Sort[]; + sorts: readonly SortDescriptor[]; /** @deprecated use `reverse` instead */ reverseType?: ReverseType; @@ -396,7 +397,7 @@ export interface IrisGridState { shownAdvancedFilter: number | null; hoverAdvancedFilter: number | null; - sorts: readonly DhType.Sort[]; + sorts: readonly SortDescriptor[]; reverse: boolean; customColumns: readonly ColumnName[]; selectDistinctColumns: readonly ColumnName[]; @@ -1421,7 +1422,7 @@ class IrisGrid extends Component { loadingScrimProgress: number | null, quickFilters: ReadonlyQuickFilterMap, advancedFilters: ReadonlyAdvancedFilterMap, - sorts: readonly DhType.Sort[], + sorts: readonly SortDescriptor[], reverse: boolean, rollupConfig: UIRollupConfig | undefined, isMenuShown: boolean @@ -1602,6 +1603,11 @@ class IrisGrid extends Component { return ''; } + /** + * Get the model column index for the provided visible index + * @param columnIndex Visible column index + * @returns Model column index, or null if not found + */ getModelColumn(columnIndex: GridRangeIndex): ModelIndex | null | undefined { const { metrics } = this.state; assertNotNull(metrics); @@ -1610,6 +1616,11 @@ class IrisGrid extends Component { return null; } + if (columnIndex != null && columnIndex < 0) { + // ColumnBy sources aren't movable, so just return the index directly + return columnIndex; + } + return columnIndex != null ? modelColumns.get(columnIndex) : null; } @@ -2842,7 +2853,7 @@ class IrisGrid extends Component { } } - updateSorts(sorts: readonly DhType.Sort[]): void { + updateSorts(sorts: readonly SortDescriptor[]): void { this.startLoading('Sorting...'); this.setState({ sorts }); this.grid?.forceUpdate(); diff --git a/packages/iris-grid/src/IrisGridModel.ts b/packages/iris-grid/src/IrisGridModel.ts index 6b660a251..a216190c3 100644 --- a/packages/iris-grid/src/IrisGridModel.ts +++ b/packages/iris-grid/src/IrisGridModel.ts @@ -13,7 +13,7 @@ import { type VisibleIndex, } from '@deephaven/grid'; import type { dh as DhType } from '@deephaven/jsapi-types'; -import { type Formatter } from '@deephaven/jsapi-utils'; +import { type Formatter, type SortDescriptor } from '@deephaven/jsapi-utils'; import { type ColumnName, type UITotalsTableConfig, @@ -274,12 +274,12 @@ abstract class IrisGridModel< /** * @returns The sorts used on this model */ - abstract get sort(): readonly DhType.Sort[]; + abstract get sort(): readonly SortDescriptor[]; /** * @param sort The sorts to use on this model */ - abstract set sort(sort: readonly DhType.Sort[]); + abstract set sort(sort: readonly SortDescriptor[]); /** /** diff --git a/packages/iris-grid/src/IrisGridModelUpdater.tsx b/packages/iris-grid/src/IrisGridModelUpdater.tsx index 4fb3d368b..617b19129 100644 --- a/packages/iris-grid/src/IrisGridModelUpdater.tsx +++ b/packages/iris-grid/src/IrisGridModelUpdater.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo } from 'react'; import type { dh } from '@deephaven/jsapi-types'; import { type ModelIndex, type MoveOperation } from '@deephaven/grid'; -import { type Formatter } from '@deephaven/jsapi-utils'; +import { type SortDescriptor, type Formatter } from '@deephaven/jsapi-utils'; import { EMPTY_ARRAY, EMPTY_MAP } from '@deephaven/utils'; import { useOnChange } from '@deephaven/react-hooks'; import IrisGridUtils from './IrisGridUtils'; @@ -29,7 +29,7 @@ interface IrisGridModelUpdaterProps { left: number | null; right: number | null; filter: readonly dh.FilterCondition[]; - sorts: readonly dh.Sort[]; + sorts: readonly SortDescriptor[]; reverse?: boolean; customColumns: readonly ColumnName[]; movedColumns: readonly MoveOperation[]; diff --git a/packages/iris-grid/src/IrisGridRenderer.ts b/packages/iris-grid/src/IrisGridRenderer.ts index bc565c9e1..0dd9adfe2 100644 --- a/packages/iris-grid/src/IrisGridRenderer.ts +++ b/packages/iris-grid/src/IrisGridRenderer.ts @@ -9,8 +9,7 @@ import { GridUtils, type VisibleIndex, } from '@deephaven/grid'; -import type { dh } from '@deephaven/jsapi-types'; -import { TableUtils } from '@deephaven/jsapi-utils'; +import { type SortDescriptor, TableUtils } from '@deephaven/jsapi-utils'; import { assertNotNull, getOrThrow } from '@deephaven/utils'; import { type AdvancedFilter, @@ -52,7 +51,7 @@ export class IrisGridRenderer extends GridRenderer { protected dataBarCellRenderer = new IrisGridDataBarCellRenderer(); - getSortIcon(sort: dh.Sort | null, size: number): Path2D | null { + getSortIcon(sort: SortDescriptor | null, size: number): Path2D | null { if (!sort) { return null; } diff --git a/packages/iris-grid/src/IrisGridTableModelTemplate.ts b/packages/iris-grid/src/IrisGridTableModelTemplate.ts index f4d64c2ce..b52e9f782 100644 --- a/packages/iris-grid/src/IrisGridTableModelTemplate.ts +++ b/packages/iris-grid/src/IrisGridTableModelTemplate.ts @@ -26,8 +26,10 @@ import { Formatter, FormatterUtils, DateUtils, + type SortDescriptor, } from '@deephaven/jsapi-utils'; import IrisGridModel, { type DisplayColumn } from './IrisGridModel'; + import AggregationOperation from './sidebar/aggregations/AggregationOperation'; import IrisGridUtils from './IrisGridUtils'; import MissingKeyError from './MissingKeyError'; @@ -1251,13 +1253,13 @@ class IrisGridTableModelTemplate< ); } - get sort(): DhType.Sort[] { + get sort(): readonly SortDescriptor[] { return this.table.sort; } - set sort(sort: DhType.Sort[]) { + set sort(sort: readonly SortDescriptor[]) { this.closeSubscription(); - this.table.applySort(sort); + this.table.applySort(this.irisGridUtils.hydrateDhSort(this.columns, sort)); this.applyViewport(); } diff --git a/packages/iris-grid/src/IrisGridTestUtils.ts b/packages/iris-grid/src/IrisGridTestUtils.ts index bbb684db1..86ebaa699 100644 --- a/packages/iris-grid/src/IrisGridTestUtils.ts +++ b/packages/iris-grid/src/IrisGridTestUtils.ts @@ -1,7 +1,8 @@ import { type GridRangeIndex, type ModelSizeMap } from '@deephaven/grid'; -import { type dh as DhType } from '@deephaven/jsapi-types'; -import { Formatter } from '@deephaven/jsapi-utils'; +import type { dh as DhType } from '@deephaven/jsapi-types'; +import { Formatter, type SortDescriptor } from '@deephaven/jsapi-utils'; import IrisGridProxyModel from './IrisGridProxyModel'; +import IrisGridUtils from './IrisGridUtils'; class IrisGridTestUtils { static DEFAULT_TYPE = 'java.lang.String'; @@ -20,8 +21,11 @@ class IrisGridTestUtils { private dh: typeof DhType; + private irisGridUtils: IrisGridUtils; + constructor(dh: typeof DhType) { this.dh = dh; + this.irisGridUtils = new IrisGridUtils(dh); } makeColumn( @@ -72,9 +76,16 @@ class IrisGridTestUtils { return new (this.dh as any).FilterCondition(); } - makeSort(): DhType.Sort { + makeSort(column: DhType.Column = this.makeColumn()): DhType.Sort { // eslint-disable-next-line @typescript-eslint/no-explicit-any - return new (this.dh as any).Sort(); + return new (this.dh as any).Sort({ column }); + } + + hydrateSort( + sortDescriptor: readonly SortDescriptor[], + columns: DhType.Column[] + ): DhType.Sort[] { + return this.irisGridUtils.hydrateDhSort(columns, sortDescriptor); } makeTable({ @@ -85,7 +96,7 @@ class IrisGridTestUtils { }: { columns?: DhType.Column[]; size?: number; - sort?: readonly DhType.Sort[]; + sort?: readonly SortDescriptor[]; layoutHints?: Partial; } = {}): DhType.Table { // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/iris-grid/src/IrisGridUtils.test.ts b/packages/iris-grid/src/IrisGridUtils.test.ts index f4663cf55..51d472eb9 100644 --- a/packages/iris-grid/src/IrisGridUtils.test.ts +++ b/packages/iris-grid/src/IrisGridUtils.test.ts @@ -185,12 +185,16 @@ describe('sort exporting/importing', () => { expect(importedSort).toEqual([ expect.objectContaining({ - column: columns[3], + column: expect.objectContaining({ + name: columns[3].name, + }), isAbs: false, direction: 'ASC', }), expect.objectContaining({ - column: columns[7], + column: expect.objectContaining({ + name: columns[7].name, + }), isAbs: true, direction: 'DESC', }), @@ -375,6 +379,15 @@ describe('remove columns in moved columns', () => { }); }); +describe('removeSortsInColumns', () => { + it('removes sort for the given column names', () => { + const table = makeTable(); + const sort = [table.columns[2].sort(), table.columns[5].sort().desc()]; + const newSort = IrisGridUtils.removeSortsInColumns(sort, ['name_2']); + expect(newSort).toEqual([table.columns[5].sort().desc()]); + }); +}); + describe('getPrevVisibleColumns', () => { const columns = irisGridTestUtils.makeColumns(5); it('returns [] for startIndex < 0', () => { diff --git a/packages/iris-grid/src/IrisGridUtils.ts b/packages/iris-grid/src/IrisGridUtils.ts index 72c890e96..32e962dd3 100644 --- a/packages/iris-grid/src/IrisGridUtils.ts +++ b/packages/iris-grid/src/IrisGridUtils.ts @@ -14,6 +14,7 @@ import { type ReverseType, type SortDirection, type FormattingRule, + type SortDescriptor, } from '@deephaven/jsapi-utils'; import Log from '@deephaven/log'; import { @@ -21,6 +22,7 @@ import { bindAllMethods, EMPTY_ARRAY, EMPTY_MAP, + isNotNullOrUndefined, } from '@deephaven/utils'; import AggregationUtils from './sidebar/aggregations/AggregationUtils'; import AggregationOperation from './sidebar/aggregations/AggregationOperation'; @@ -380,7 +382,7 @@ class IrisGridUtils { * @param sorts The table sorts * @returns The dehydrated sorts */ - static dehydrateSort(sorts: readonly DhType.Sort[]): DehydratedSort[] { + static dehydrateSort(sorts: readonly SortDescriptor[]): DehydratedSort[] { return sorts.map(sort => { const { column, isAbs, direction } = sort; return { @@ -484,9 +486,9 @@ class IrisGridUtils { } static removeSortsInColumns( - sorts: readonly DhType.Sort[], + sorts: readonly SortDescriptor[], columnNames: readonly string[] - ): DhType.Sort[] { + ): readonly SortDescriptor[] { return sorts.filter(sort => !columnNames.includes(sort.column.name)); } @@ -924,7 +926,10 @@ class IrisGridUtils { columns: readonly DhType.Column[], columnName: ColumnName ): DhType.Column | undefined { - const column = columns.find(({ name }) => name === columnName); + // Columns can contain ColumnBy sources with negative indexes + const column = Object.values(columns).find( + ({ name }) => name === columnName + ); if (column == null) { log.error( 'Unable to retrieve column by name', @@ -1052,7 +1057,10 @@ class IrisGridUtils { assertNotNull(name, 'Column header group has no name'); - if (model.getColumnIndexByName(name) != null) { + const columnIndex = model.getColumnIndexByName(name); + // Groups cannot have names matching columns to avoid parent-child collision + // unless they refer to ColumnBy sources with negative indexes + if (columnIndex != null && columnIndex >= 0) { throw new Error(`Column header group has same name as column: ${name}`); } @@ -1306,7 +1314,7 @@ class IrisGridUtils { quickFilters, formatter.timeZone ), - sorts: this.hydrateSort(columns, sorts), + sorts: this.hydrateSort(columns, sorts, true), userColumnWidths: new Map( userColumnWidths .map( @@ -1642,15 +1650,17 @@ class IrisGridUtils { } /** - * Import the saved sorts to apply to the table. Does not actually apply the sort. - * @param columns The columns the sorts will be applied to - * @param sorts Exported sort definitions - * @returns The sorts to apply to the table + * Import the saved sorts to apply to the model. Does not actually apply the sort. + * @param columns The columns the sorts will be applied to + * @param sorts Exported sort definitions + * @param dropReverse If true, drop reverse sort descriptors from the result + * @returns The sort descriptors to apply to the model */ hydrateSort( columns: readonly DhType.Column[], - sorts: readonly (DehydratedSort | LegacyDehydratedSort)[] - ): DhType.Sort[] { + sorts: readonly (DehydratedSort | LegacyDehydratedSort)[], + dropReverse = false + ): readonly SortDescriptor[] { const { dh } = this; return ( sorts @@ -1665,6 +1675,51 @@ class IrisGridUtils { ? IrisGridUtils.getColumnByName(columns, columnIndexOrName) : IrisGridUtils.getColumn(columns, columnIndexOrName); + if (column != null) { + return { + column: { + name: column.name, + type: column.type, + }, + isAbs, + direction, + }; + } + return null; + }) + // If we can't find the column any more, it's null, filter it out + // If the item is a reverse sort item, and dropReverse is true, filter it out + // - it will get applied with the `reverse` property + // This should only happen when loading a legacy dashboard + .filter( + item => + item != null && + (item.direction !== TableUtils.sortDirection.reverse || + !dropReverse) + ) as SortDescriptor[] + ); + } + + hydrateDhSort( + columns: readonly DhType.Column[], + sorts: readonly SortDescriptor[] + ): DhType.Sort[] { + const { dh } = this; + return ( + sorts + .map(sortDescriptor => { + const { + column: { name }, + isAbs, + direction, + } = sortDescriptor; + + if (direction === TableUtils.sortDirection.reverse) { + return dh.Table.reverse(); + } + + const column = IrisGridUtils.getColumnByName(columns, name); + if (column != null) { let columnSort = column.sort(); if (isAbs) { @@ -1681,12 +1736,7 @@ class IrisGridUtils { return null; }) // If we can't find the column any more, it's null, filter it out - // If the item is a reverse sort item, filter it out - it will get applied with the `reverse` property - // This should only happen when loading a legacy dashboard - .filter( - item => - item != null && item.direction !== TableUtils.sortDirection.reverse - ) as DhType.Sort[] + .filter(isNotNullOrUndefined) as DhType.Sort[] ); } @@ -1728,7 +1778,10 @@ class IrisGridUtils { let sorts: DhType.Sort[] = []; if (tableSettings.sorts) { - sorts = this.hydrateSort(columns, tableSettings.sorts); + sorts = this.hydrateDhSort( + columns, + this.hydrateSort(columns, tableSettings.sorts) + ); } let filters = [...quickFilters, ...advancedFilters]; diff --git a/packages/iris-grid/src/mousehandlers/IrisGridContextMenuHandler.tsx b/packages/iris-grid/src/mousehandlers/IrisGridContextMenuHandler.tsx index 066e33d87..7a0c6f752 100644 --- a/packages/iris-grid/src/mousehandlers/IrisGridContextMenuHandler.tsx +++ b/packages/iris-grid/src/mousehandlers/IrisGridContextMenuHandler.tsx @@ -38,6 +38,7 @@ import { type TableColumnFormat, type IntegerColumnFormat, type SortDirection, + type SortDescriptor, } from '@deephaven/jsapi-utils'; import Log from '@deephaven/log'; import type { DebouncedFunc } from 'lodash'; @@ -2076,7 +2077,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler { sortByActions( column: DhType.Column, modelColumn: ModelIndex, - columnSort: DhType.Sort | null + columnSort: SortDescriptor | null ): ContextAction[] { const theme = this.irisGrid.getTheme(); const { contextMenuSortIconColor } = theme; @@ -2252,7 +2253,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler { } checkColumnSort( - columnSort?: DhType.Sort | null, + columnSort?: SortDescriptor | null, direction: SortDirection = null, isAbs = false ): boolean { diff --git a/packages/jsapi-utils/src/TableUtils.test.ts b/packages/jsapi-utils/src/TableUtils.test.ts index 05feac0a1..47c330e61 100644 --- a/packages/jsapi-utils/src/TableUtils.test.ts +++ b/packages/jsapi-utils/src/TableUtils.test.ts @@ -8,7 +8,11 @@ import { } from '@deephaven/filters'; import { getAllMethodNames } from '@deephaven/utils'; import { TestUtils } from '@deephaven/test-utils'; -import TableUtils, { type DataType, type SortDirection } from './TableUtils'; +import TableUtils, { + type DataType, + type SortDirection, + type SortDescriptor, +} from './TableUtils'; import DateUtils from './DateUtils'; // eslint-disable-next-line import/no-relative-packages import IrisGridTestUtils from '../../iris-grid/src/IrisGridTestUtils'; @@ -842,47 +846,77 @@ describe('toggleSortForColumn', () => { const columns = makeColumns(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const table: Table = new (dh as any).Table({ columns }); - let tableSorts: Sort[] = []; + let sortDescriptors: SortDescriptor[] = []; expect(table).not.toBe(null); expect(table.sort.length).toBe(0); - tableSorts = TableUtils.toggleSortForColumn(tableSorts, columns, 0, true); - table.applySort(tableSorts); + sortDescriptors = TableUtils.toggleSortForColumn( + sortDescriptors, + columns, + 0, + true + ); + table.applySort(irisGridTestUtils.hydrateSort(sortDescriptors, columns)); expect(table.sort.length).toBe(1); expect(table.sort[0].column).toBe(columns[0]); expect(table.sort[0].direction).toBe(TableUtils.sortDirection.ascending); - tableSorts = TableUtils.toggleSortForColumn(tableSorts, columns, 3, true); - table.applySort(tableSorts); + sortDescriptors = TableUtils.toggleSortForColumn( + sortDescriptors, + columns, + 3, + true + ); + table.applySort(irisGridTestUtils.hydrateSort(sortDescriptors, columns)); expect(table.sort.length).toBe(2); expect(table.sort[0].column).toBe(columns[0]); expect(table.sort[0].direction).toBe(TableUtils.sortDirection.ascending); expect(table.sort[1].column).toBe(columns[3]); expect(table.sort[1].direction).toBe(TableUtils.sortDirection.ascending); - tableSorts = TableUtils.toggleSortForColumn(tableSorts, columns, 0, true); - table.applySort(tableSorts); + sortDescriptors = TableUtils.toggleSortForColumn( + sortDescriptors, + columns, + 0, + true + ); + table.applySort(irisGridTestUtils.hydrateSort(sortDescriptors, columns)); expect(table.sort.length).toBe(2); expect(table.sort[0].column).toBe(columns[3]); expect(table.sort[0].direction).toBe(TableUtils.sortDirection.ascending); expect(table.sort[1].column).toBe(columns[0]); expect(table.sort[1].direction).toBe(TableUtils.sortDirection.descending); - tableSorts = TableUtils.toggleSortForColumn(tableSorts, columns, 0, true); - table.applySort(tableSorts); + sortDescriptors = TableUtils.toggleSortForColumn( + sortDescriptors, + columns, + 0, + true + ); + table.applySort(irisGridTestUtils.hydrateSort(sortDescriptors, columns)); expect(table.sort.length).toBe(1); expect(table.sort[0].column).toBe(columns[3]); expect(table.sort[0].direction).toBe(TableUtils.sortDirection.ascending); - tableSorts = TableUtils.toggleSortForColumn(tableSorts, columns, 3, true); - table.applySort(tableSorts); + sortDescriptors = TableUtils.toggleSortForColumn( + sortDescriptors, + columns, + 3, + true + ); + table.applySort(irisGridTestUtils.hydrateSort(sortDescriptors, columns)); expect(table.sort.length).toBe(1); expect(table.sort[0].column).toBe(columns[3]); expect(table.sort[0].direction).toBe(TableUtils.sortDirection.descending); - tableSorts = TableUtils.toggleSortForColumn(tableSorts, columns, 3, true); - table.applySort(tableSorts); + sortDescriptors = TableUtils.toggleSortForColumn( + sortDescriptors, + columns, + 3, + true + ); + table.applySort(irisGridTestUtils.hydrateSort(sortDescriptors, columns)); expect(table.sort.length).toBe(0); }); @@ -891,6 +925,43 @@ describe('toggleSortForColumn', () => { const columns = makeColumns(); expect(TableUtils.toggleSortForColumn([], columns, -1)).toEqual([]); }); + + it('handles negative columnIndex correctly', () => { + const columns = makeColumns(); + const colBySources = makeColumns(); + colBySources.forEach((col, index) => { + columns[-index - 1] = col; + }); + + expect(columns[-1]).toEqual(colBySources[0]); + + let sorts: SortDescriptor[] = []; + sorts = TableUtils.toggleSortForColumn(sorts, columns, -1); + + expect(sorts).toEqual([ + expect.objectContaining({ + column: { + name: colBySources[0].name, + type: colBySources[0].type, + }, + direction: 'ASC', + }), + ]); + + sorts = TableUtils.toggleSortForColumn(sorts, columns, -1); + expect(sorts).toEqual([ + expect.objectContaining({ + column: { + name: colBySources[0].name, + type: colBySources[0].type, + }, + direction: 'DESC', + }), + ]); + + sorts = TableUtils.toggleSortForColumn(sorts, columns, -1); + expect(sorts).toEqual([]); + }); }); describe('quick filter tests', () => { @@ -3290,7 +3361,7 @@ describe('makeColumnSort', () => { columnIndex: number, direction: SortDirection, isAbs: boolean, - expectedValue: Partial | null + expectedValue: SortDescriptor | null ) => { expect( TableUtils.makeColumnSort(columns, columnIndex, direction, isAbs) @@ -3310,8 +3381,10 @@ describe('makeColumnSort', () => { it('should return an ascending sort if direction is ASC', () => { const columns = makeColumns(); - const expectedValue: Partial = { - column: columns[0], + const expectedValue: SortDescriptor = { + column: expect.objectContaining({ + name: columns[0].name, + }), direction: 'ASC', isAbs: true, }; @@ -3320,8 +3393,10 @@ describe('makeColumnSort', () => { it('should return an descending sort if direction is DESC', () => { const columns = makeColumns(); - const expectedValue: Partial = { - column: columns[1], + const expectedValue: SortDescriptor = { + column: expect.objectContaining({ + name: columns[1].name, + }), direction: 'DESC', isAbs: false, }; @@ -3330,9 +3405,11 @@ describe('makeColumnSort', () => { it('should return the default sort if direction is REVERSE', () => { const columns = makeColumns(); - const expectedValue: Partial = { - column: columns[2], - direction: 'ASC', + const expectedValue: SortDescriptor = { + column: expect.objectContaining({ + name: columns[2].name, + }), + direction: 'REVERSE', isAbs: true, }; testMakeColumnSort(columns, 2, 'REVERSE', true, expectedValue); @@ -3403,7 +3480,7 @@ describe('sortColumn', () => { const columns = makeColumns(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const table: Table = new (dh as any).Table({ columns }); - let tableSorts: Sort[] = []; + let tableSorts: SortDescriptor[] = []; expect(table).not.toBe(null); expect(table.sort.length).toBe(0); @@ -3416,7 +3493,7 @@ describe('sortColumn', () => { true, true ); - table.applySort(tableSorts); + table.applySort(irisGridTestUtils.hydrateSort(tableSorts, columns)); expect(table.sort.length).toBe(1); expect(table.sort[0].column).toBe(columns[0]); expect(table.sort[0].direction).toBe(TableUtils.sortDirection.ascending); @@ -3430,7 +3507,7 @@ describe('sortColumn', () => { false, true ); - table.applySort(tableSorts); + table.applySort(irisGridTestUtils.hydrateSort(tableSorts, columns)); expect(table.sort.length).toBe(2); expect(table.sort[0].column).toBe(columns[0]); expect(table.sort[0].direction).toBe(TableUtils.sortDirection.ascending); @@ -3447,7 +3524,7 @@ describe('sortColumn', () => { false, true ); - table.applySort(tableSorts); + table.applySort(irisGridTestUtils.hydrateSort(tableSorts, columns)); expect(table.sort.length).toBe(2); expect(table.sort[0].column).toBe(columns[3]); expect(table.sort[0].direction).toBe(TableUtils.sortDirection.ascending); @@ -3464,7 +3541,7 @@ describe('sortColumn', () => { false, false ); - table.applySort(tableSorts); + table.applySort(irisGridTestUtils.hydrateSort(tableSorts, columns)); expect(table.sort.length).toBe(1); expect(table.sort[0].column).toBe(columns[3]); expect(table.sort[0].direction).toBe(TableUtils.sortDirection.descending); diff --git a/packages/jsapi-utils/src/TableUtils.ts b/packages/jsapi-utils/src/TableUtils.ts index ecbb42aa3..562137d62 100644 --- a/packages/jsapi-utils/src/TableUtils.ts +++ b/packages/jsapi-utils/src/TableUtils.ts @@ -36,6 +36,16 @@ export interface FilterItem { value: string; } +// Sort descriptor loosely matching the shape of dh.Sort +export interface SortDescriptor { + column: { + name: ColumnName; + type: string; + }; + isAbs: boolean; + direction: string; +} + export type AdvancedFilterOptions = { filterItems: FilterItem[]; filterOperators: FilterOperatorValue[]; @@ -154,7 +164,7 @@ export class TableUtils { }; static getSortIndex( - sort: readonly DhType.Sort[], + sort: readonly SortDescriptor[], columnName: ColumnName ): number | null { for (let i = 0; i < sort.length; i += 1) { @@ -173,9 +183,9 @@ export class TableUtils { * @returns The sort for the column, or null if it's not sorted */ static getSortForColumn( - tableSort: readonly DhType.Sort[], + tableSort: readonly SortDescriptor[], columnName: ColumnName - ): DhType.Sort | null { + ): SortDescriptor | null { const sortIndex = TableUtils.getSortIndex(tableSort, columnName); if (sortIndex != null) { return tableSort[sortIndex]; @@ -226,19 +236,29 @@ export class TableUtils { static getNextSort( columns: readonly DhType.Column[], - sorts: readonly DhType.Sort[], + sorts: readonly SortDescriptor[], columnIndex: number - ): DhType.Sort | null { - if (columnIndex < 0 || columnIndex >= columns.length) { + ): SortDescriptor | null { + const column = columns[columnIndex]; + if (column == null) { return null; } - - const sort = TableUtils.getSortForColumn(sorts, columns[columnIndex].name); + const sort = TableUtils.getSortForColumn(sorts, column.name); if (sort === null) { - return columns[columnIndex].sort().asc(); + return this.makeColumnSort( + columns, + columnIndex, + TableUtils.sortDirection.ascending, + false + ); } if (sort.direction === TableUtils.sortDirection.ascending) { - return sort.desc(); + return this.makeColumnSort( + columns, + columnIndex, + TableUtils.sortDirection.descending, + false + ); } return null; } @@ -248,8 +268,9 @@ export class TableUtils { columnIndex: number, direction: SortDirection, isAbs: boolean - ): DhType.Sort | null { - if (columnIndex < 0 || columnIndex >= columns.length) { + ): SortDescriptor | null { + const column = columns[columnIndex]; + if (column == null) { return null; } @@ -257,22 +278,14 @@ export class TableUtils { return null; } - let sort = columns[columnIndex].sort(); - - switch (direction) { - case TableUtils.sortDirection.ascending: - sort = sort.asc(); - break; - case TableUtils.sortDirection.descending: - sort = sort.desc(); - break; - default: - break; - } - if (isAbs) { - sort = sort.abs(); - } - return sort; + return { + column: { + name: column.name, + type: column.type, + }, + isAbs, + direction, + }; } /** @@ -283,12 +296,12 @@ export class TableUtils { * @param addToExisting Add this sort to the existing sort */ static toggleSortForColumn( - sorts: readonly DhType.Sort[], + sorts: readonly SortDescriptor[], columns: readonly DhType.Column[], columnIndex: number, addToExisting = false - ): DhType.Sort[] { - if (columnIndex < 0 || columnIndex >= columns.length) { + ): readonly SortDescriptor[] { + if (columns[columnIndex] == null) { return []; } @@ -303,13 +316,13 @@ export class TableUtils { } static sortColumn( - sorts: readonly DhType.Sort[], + sorts: readonly SortDescriptor[], columns: readonly DhType.Column[], modelColumn: number, direction: SortDirection, isAbs: boolean, addToExisting: boolean - ): DhType.Sort[] { + ): readonly SortDescriptor[] { if (modelColumn < 0 || modelColumn >= columns.length) { return []; } @@ -338,13 +351,13 @@ export class TableUtils { * @returns Returns the modified array of sorts - removing reverses */ static setSortForColumn( - tableSort: readonly DhType.Sort[], + tableSort: readonly SortDescriptor[], columnName: ColumnName, - sort: DhType.Sort | null, + sort: SortDescriptor | null, addToExisting = false - ): DhType.Sort[] { + ): readonly SortDescriptor[] { const sortIndex = TableUtils.getSortIndex(tableSort, columnName); - let sorts: DhType.Sort[] = []; + let sorts: SortDescriptor[] = []; if (addToExisting) { sorts = sorts.concat( tableSort.filter(