Skip to content

Commit de5ce40

Browse files
authored
feat: Partitioned Table UI Enhancements (#2110)
Resolves #2079 ( Resolves #2066 , Resolves #2103 , Resolves #2104 , Resolves #2105 , Resolves #2106 , Resolves #2107 , Resolves #2108 , Resolves #2109 , Resolves #2049 ), Resolves #2120, Resolves #1904 **Changes Implemented:** - Replaced `keysTable` with `baseTable` - Made small UI changes to differentiate between Partition Table and Partition-aware source table - Allow double clicking to set partition - Add context menu action to set partition **Sample Visuals:** _Default Partition Table View_ <img width="798" alt="Screenshot 2024-06-26 at 2 57 09 PM" src="https://github.com/deephaven/web-client-ui/assets/69530774/19059a9f-7e69-470a-818e-c1d6d9ce8118"> _Default Partition-aware source table view:_ <img width="789" alt="Screenshot 2024-06-26 at 2 56 59 PM" src="https://github.com/deephaven/web-client-ui/assets/69530774/af891a26-3395-4842-963f-c8b2f1c9186d">
1 parent 3fccd43 commit de5ce40

11 files changed

Lines changed: 252 additions & 135 deletions

packages/iris-grid/src/IrisGrid.tsx

Lines changed: 81 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ import {
116116
IrisGridContextMenuHandler,
117117
IrisGridCopyCellMouseHandler,
118118
IrisGridDataSelectMouseHandler,
119+
IrisGridPartitionedTableMouseHandler,
119120
IrisGridFilterMouseHandler,
120121
IrisGridRowTreeMouseHandler,
121122
IrisGridSortMouseHandler,
@@ -734,6 +735,7 @@ class IrisGrid extends Component<IrisGridProps, IrisGridState> {
734735
new IrisGridContextMenuHandler(this, dh),
735736
new IrisGridDataSelectMouseHandler(this),
736737
new PendingMouseHandler(this),
738+
new IrisGridPartitionedTableMouseHandler(this),
737739
];
738740
if (canCopy) {
739741
keyHandlers.push(new CopyKeyHandler(this));
@@ -1781,12 +1783,12 @@ class IrisGrid extends Component<IrisGridProps, IrisGridState> {
17811783
* Rebuilds all the current filters. Necessary if something like the time zone has changed.
17821784
*/
17831785
rebuildFilters(): void {
1784-
log.debug('Rebuilding filters');
1785-
17861786
const { model } = this.props;
17871787
const { advancedFilters, quickFilters } = this.state;
17881788
const { columns, formatter } = model;
17891789

1790+
log.debug('Rebuilding filters');
1791+
17901792
const newAdvancedFilters = new Map();
17911793
const newQuickFilters = new Map();
17921794

@@ -1812,8 +1814,8 @@ class IrisGrid extends Component<IrisGridProps, IrisGridState> {
18121814
filter: this.makeQuickFilter(column, text, formatter.timeZone),
18131815
});
18141816
});
1815-
18161817
this.startLoading('Rebuilding filters...', { resetRanges: true });
1818+
18171819
this.setState({
18181820
quickFilters: newQuickFilters,
18191821
advancedFilters: newAdvancedFilters,
@@ -2024,11 +2026,15 @@ class IrisGrid extends Component<IrisGridProps, IrisGridState> {
20242026

20252027
async loadPartitionsTable(model: PartitionedGridModel): Promise<void> {
20262028
try {
2027-
const partitionConfig = await this.getInitialPartitionConfig(model);
2028-
this.setState(
2029-
{ isSelectingPartition: true, partitionConfig },
2030-
this.loadTableState
2031-
);
2029+
const { partitionConfig } = this.state;
2030+
if (!partitionConfig) {
2031+
this.startLoading('Loading partitions...', {
2032+
loadingCancelShown: false,
2033+
resetRanges: true,
2034+
});
2035+
this.initializePartitionConfig(model);
2036+
}
2037+
this.setState({ isSelectingPartition: true }, this.loadTableState);
20322038
} catch (error) {
20332039
if (!PromiseUtils.isCanceled(error)) {
20342040
this.handleTableLoadError(error);
@@ -2037,19 +2043,9 @@ class IrisGrid extends Component<IrisGridProps, IrisGridState> {
20372043
}
20382044

20392045
/**
2040-
* Gets the initial partition config for the currently set model.
2041-
* Sorts the key table and gets the first key.
2042-
* If the table is ticking, it will wait for the first tick.
2046+
* Initialize the partition config to the default partition.
20432047
*/
2044-
async getInitialPartitionConfig(
2045-
model: PartitionedGridModel
2046-
): Promise<PartitionConfig> {
2047-
const { partitionConfig } = this.state;
2048-
if (partitionConfig !== undefined) {
2049-
// User already has a partition selected, just use that
2050-
return partitionConfig;
2051-
}
2052-
2048+
async initializePartitionConfig(model: PartitionedGridModel): Promise<void> {
20532049
const keyTable = await this.pending.add(
20542050
model.partitionKeysTable(),
20552051
resolved => resolved.close()
@@ -2060,37 +2056,63 @@ class IrisGrid extends Component<IrisGridProps, IrisGridState> {
20602056
keyTable.applySort(sorts);
20612057
keyTable.setViewport(0, 0);
20622058

2063-
return new Promise((resolve, reject) => {
2064-
// We want to wait for the first UPDATED event instead of just getting viewport data here
2065-
// It's possible that the key table does not have any rows of data yet, so just wait until it does have one
2066-
keyTable.addEventListener(
2067-
dh.Table.EVENT_UPDATED,
2068-
(event: CustomEvent<DhType.ViewportData>) => {
2069-
try {
2070-
const { detail: data } = event;
2071-
if (data.rows.length === 0) {
2072-
// Table is empty, wait for the next updated event
2073-
return;
2074-
}
2075-
const row = data.rows[0];
2076-
// Core JSAPI returns undefined for null table values, IrisGridPartitionSelector expects null
2077-
// https://github.com/deephaven/deephaven-core/issues/5400
2078-
const values = keyTable.columns.map(
2079-
column => row.get(column) ?? null
2080-
);
2081-
const newPartition: PartitionConfig = {
2082-
partitions: values,
2083-
mode: 'partition',
2084-
};
2085-
keyTable.close();
2086-
resolve(newPartition);
2087-
} catch (e) {
2088-
keyTable.close();
2089-
reject(e);
2059+
// We want to wait for the first UPDATED event instead of just getting viewport data here
2060+
// It's possible that the key table does not have any rows of data yet, so just wait until it does have one
2061+
keyTable.addEventListener(
2062+
dh.Table.EVENT_UPDATED,
2063+
(event: CustomEvent<DhType.ViewportData>) => {
2064+
try {
2065+
const { detail: data } = event;
2066+
if (data.rows.length === 0) {
2067+
// We received an update and the table is still empty. Stop showing the loading spinner so we know
2068+
this.stopLoading();
2069+
return;
20902070
}
2071+
const row = data.rows[0];
2072+
const values = keyTable.columns.map(column => row.get(column));
2073+
const newPartition: PartitionConfig = {
2074+
partitions: values,
2075+
mode: model.isPartitionAwareSourceTable ? 'partition' : 'keys',
2076+
};
2077+
keyTable.close();
2078+
this.setState({
2079+
loadingSpinnerShown: false,
2080+
partitionConfig: newPartition,
2081+
});
2082+
} catch (e) {
2083+
keyTable.close();
2084+
log.error('Error getting initial partition config', e);
20912085
}
2086+
}
2087+
);
2088+
}
2089+
2090+
/**
2091+
* Selects a partition key from the table based on the provided row index.
2092+
*
2093+
* @param rowIndex The index of the row from which the partition will be selected.
2094+
* @returns A promise that resolves when the partition key has been successfully selected.
2095+
*/
2096+
selectPartitionKeyFromTable(rowIndex: GridRangeIndex): void {
2097+
const { model } = this.props;
2098+
assertNotNull(rowIndex);
2099+
if (isPartitionedGridModel(model)) {
2100+
const data = this.getRowDataMap(rowIndex);
2101+
const partitionColumnSet = new Set(
2102+
model.partitionColumns.map(column => column.name)
20922103
);
2093-
});
2104+
const values = Object.entries(data)
2105+
.filter(([key, _]) => partitionColumnSet.has(key))
2106+
.map(([key, columnData]) => columnData.value);
2107+
const newPartition: PartitionConfig = {
2108+
partitions: values,
2109+
mode: 'partition',
2110+
};
2111+
this.setState({
2112+
isSelectingPartition: true,
2113+
partitionConfig: newPartition,
2114+
});
2115+
}
20942116
}
20952117

20962118
copyCell(
@@ -2532,7 +2554,10 @@ class IrisGrid extends Component<IrisGridProps, IrisGridState> {
25322554

25332555
handlePartitionChange(partitionConfig: PartitionConfig): void {
25342556
this.startLoading('Partitioning...');
2535-
this.setState({ partitionConfig });
2557+
const { partitionConfig: prevConfig } = this.state;
2558+
if (prevConfig !== partitionConfig) {
2559+
this.setState({ partitionConfig });
2560+
}
25362561
}
25372562

25382563
handleTableLoadError(error: unknown): void {
@@ -4700,15 +4725,13 @@ class IrisGrid extends Component<IrisGridProps, IrisGridState> {
47004725
unmountOnExit
47014726
>
47024727
<div className="iris-grid-partition-selector-wrapper iris-grid-bar iris-grid-bar-primary">
4703-
{isPartitionedGridModel(model) &&
4704-
model.isPartitionRequired &&
4705-
partitionConfig && (
4706-
<IrisGridPartitionSelector
4707-
model={model}
4708-
partitionConfig={partitionConfig}
4709-
onChange={this.handlePartitionChange}
4710-
/>
4711-
)}
4728+
{isPartitionedGridModel(model) && model.isPartitionRequired && (
4729+
<IrisGridPartitionSelector
4730+
model={model}
4731+
partitionConfig={partitionConfig}
4732+
onChange={this.handlePartitionChange}
4733+
/>
4734+
)}
47124735
</div>
47134736
</CSSTransition>
47144737
<CSSTransition

packages/iris-grid/src/IrisGridPartitionSelector.scss

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
}
1919
.partition-button-group {
2020
display: flex;
21-
border: 1px var(--dh-color-hr);
22-
border-style: none solid;
21+
border-right: 1px solid var(--dh-color-hr);
2322
padding: 0 $spacer-2;
2423
gap: $spacer-2;
2524
}

packages/iris-grid/src/IrisGridPartitionSelector.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ function makeModel(
1717
Promise.resolve(irisGridTestUtils.makeTable())
1818
),
1919
partitionColumns: columns,
20+
partitionBaseTable: jest.fn(() =>
21+
Promise.resolve(irisGridTestUtils.makeTable())
22+
),
2023
} as unknown as PartitionedGridModel;
2124
return model;
2225
}

0 commit comments

Comments
 (0)